diff --git a/Moncter/src/pages/pc/ckbox/weChat/api.ts b/Moncter/src/pages/pc/ckbox/weChat/api.ts new file mode 100644 index 000000000..e69de29bb diff --git a/Moncter/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx b/Moncter/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/Moncter/src/store/module/websocket/websocket.ts b/Moncter/src/store/module/websocket/websocket.ts new file mode 100644 index 000000000..e69de29bb diff --git a/Touchkebao/src/components/TwoColumnSelection/TwoColumnSelection.tsx b/Touchkebao/src/components/TwoColumnSelection/TwoColumnSelection.tsx index e5400bf60..e1475cdcb 100644 --- a/Touchkebao/src/components/TwoColumnSelection/TwoColumnSelection.tsx +++ b/Touchkebao/src/components/TwoColumnSelection/TwoColumnSelection.tsx @@ -17,6 +17,7 @@ const FriendListItem = memo<{ onClick={() => onSelect(friend)} > +     {friend.nickname?.charAt(0)} @@ -41,6 +42,9 @@ interface TwoColumnSelectionProps { deviceIds?: number[]; enableDeviceFilter?: boolean; dataSource?: FriendSelectionItem[]; + onLoadMore?: () => void; // 加载更多回调 + hasMore?: boolean; // 是否有更多数据 + loading?: boolean; // 是否正在加载 } const TwoColumnSelection: React.FC = ({ @@ -51,15 +55,16 @@ const TwoColumnSelection: React.FC = ({ deviceIds = [], enableDeviceFilter = true, dataSource, + onLoadMore, + hasMore = false, + loading = false, }) => { const [rawFriends, setRawFriends] = useState([]); const [selectedFriends, setSelectedFriends] = useState( [], ); const [searchQuery, setSearchQuery] = useState(""); - const [loading, setLoading] = useState(false); - const [currentPage, setCurrentPage] = useState(1); - const [totalPages, setTotalPages] = useState(1); + const [isLoading, setIsLoading] = useState(false); // 使用 useMemo 缓存过滤结果,避免每次渲染都重新计算 const filteredFriends = useMemo(() => { @@ -76,17 +81,8 @@ const TwoColumnSelection: React.FC = ({ ); }, [dataSource, rawFriends, searchQuery]); - // 分页显示好友列表,避免一次性渲染太多项目 - const ITEMS_PER_PAGE = 50; - const [displayPage, setDisplayPage] = useState(1); - - const friends = useMemo(() => { - const startIndex = 0; - const endIndex = displayPage * ITEMS_PER_PAGE; - return filteredFriends.slice(startIndex, endIndex); - }, [filteredFriends, displayPage]); - - const hasMoreFriends = filteredFriends.length > friends.length; + // 好友列表直接使用过滤后的结果 + const friends = filteredFriends; // 使用 useMemo 缓存选中状态映射,避免每次渲染都重新计算 const selectedFriendsMap = useMemo(() => { @@ -100,7 +96,7 @@ const TwoColumnSelection: React.FC = ({ // 获取好友列表 const fetchFriends = useCallback( async (page: number, keyword: string = "") => { - setLoading(true); + setIsLoading(true); try { const params: any = { page, @@ -119,7 +115,6 @@ const TwoColumnSelection: React.FC = ({ if (response.success) { setRawFriends(response.data.list || []); - setTotalPages(Math.ceil((response.data.total || 0) / 20)); } else { setRawFriends([]); message.error(response.message || "获取好友列表失败"); @@ -128,7 +123,7 @@ const TwoColumnSelection: React.FC = ({ console.error("获取好友列表失败:", error); message.error("获取好友列表失败"); } finally { - setLoading(false); + setIsLoading(false); } }, [deviceIds, enableDeviceFilter], @@ -139,7 +134,6 @@ const TwoColumnSelection: React.FC = ({ if (visible && !dataSource) { // 只有在没有外部数据源时才调用 API fetchFriends(1); - setCurrentPage(1); } }, [visible, dataSource, fetchFriends]); @@ -148,49 +142,23 @@ const TwoColumnSelection: React.FC = ({ if (visible) { setSearchQuery(""); setSelectedFriends([]); - setLoading(false); + setIsLoading(false); } }, [visible]); // 防抖搜索处理 - const handleSearch = useCallback(() => { - let timeoutId: NodeJS.Timeout; - return (value: string) => { - clearTimeout(timeoutId); - timeoutId = setTimeout(() => { - setDisplayPage(1); // 重置分页 - if (!dataSource) { - fetchFriends(1, value); - } - }, 300); - }; - }, [dataSource, fetchFriends])(); - - // API搜索处理(当没有外部数据源时) - const handleApiSearch = useCallback( - async (keyword: string) => { + const handleSearch = useCallback( + (value: string) => { if (!dataSource) { - await fetchFriends(1, keyword); + const timer = setTimeout(() => { + fetchFriends(1, value); + }, 300); + return () => clearTimeout(timer); } }, [dataSource, fetchFriends], ); - // 加载更多好友 - const handleLoadMore = useCallback(() => { - setDisplayPage(prev => prev + 1); - }, []); - - // 防抖搜索 - useEffect(() => { - if (!dataSource && searchQuery.trim()) { - const timer = setTimeout(() => { - handleApiSearch(searchQuery); - }, 300); - return () => clearTimeout(timer); - } - }, [searchQuery, dataSource, handleApiSearch]); - // 选择好友 - 使用 useCallback 优化性能 const handleSelectFriend = useCallback((friend: FriendSelectionItem) => { setSelectedFriends(prev => { @@ -216,10 +184,8 @@ const TwoColumnSelection: React.FC = ({ setSearchQuery(""); }, [selectedFriends, onConfirm]); - // 取消选择 - 使用 useCallback 优化性能 + // 取消选择 const handleCancel = useCallback(() => { - setSelectedFriends([]); - setSearchQuery(""); onCancel(); }, [onCancel]); @@ -248,8 +214,8 @@ const TwoColumnSelection: React.FC = ({ value={searchQuery} onChange={e => { const value = e.target.value; - setSearchQuery(value); // 立即更新显示 - handleSearch(value); // 防抖处理搜索 + setSearchQuery(value); + handleSearch(value); }} prefix={} allowClear @@ -257,7 +223,7 @@ const TwoColumnSelection: React.FC = ({
- {loading ? ( + {isLoading && !loading ? (
加载中...
) : friends.length > 0 ? ( // 使用 React.memo 优化列表项渲染 @@ -280,9 +246,10 @@ const TwoColumnSelection: React.FC = ({
)} - {hasMoreFriends && ( + {/* 使用外部传入的加载更多 */} + {hasMore && (
-
diff --git a/Touchkebao/src/main.tsx b/Touchkebao/src/main.tsx index a3392df28..dde8d3ad6 100644 --- a/Touchkebao/src/main.tsx +++ b/Touchkebao/src/main.tsx @@ -7,97 +7,18 @@ import dayjs from "dayjs"; import "dayjs/locale/zh-cn"; import App from "./App"; import "./styles/global.scss"; -import { db } from "./utils/db"; // 引入数据库实例 +import { initializeDatabaseFromPersistedUser } from "./utils/db"; // 设置dayjs为中文 dayjs.locale("zh-cn"); -// 清理旧数据库 -async function cleanupOldDatabase() { +async function bootstrap() { try { - // 获取所有数据库 - const databases = await indexedDB.databases(); - - for (const dbInfo of databases) { - if (dbInfo.name === "CunkebaoDatabase") { - console.log("检测到旧版数据库,开始清理..."); - - // 打开数据库检查版本 - const openRequest = indexedDB.open(dbInfo.name); - - await new Promise((resolve, reject) => { - openRequest.onsuccess = async event => { - const database = (event.target as IDBOpenDBRequest).result; - const objectStoreNames = Array.from(database.objectStoreNames); - - // 检查是否存在旧表 - const hasOldTables = objectStoreNames.some(name => - [ - "kfUsers", - "weChatGroup", - "contracts", - "newContactList", - "messageList", - ].includes(name), - ); - - if (hasOldTables) { - console.log("发现旧表,删除整个数据库:", objectStoreNames); - database.close(); - - // 删除整个数据库 - const deleteRequest = indexedDB.deleteDatabase(dbInfo.name); - deleteRequest.onsuccess = () => { - console.log("旧数据库已删除"); - resolve(); - }; - deleteRequest.onerror = () => { - console.error("删除旧数据库失败"); - reject(); - }; - } else { - console.log("数据库结构正确,无需清理"); - database.close(); - resolve(); - } - }; - - openRequest.onerror = () => { - console.error("无法打开数据库进行检查"); - reject(); - }; - }); - } - } + await initializeDatabaseFromPersistedUser(); } catch (error) { - console.warn("清理旧数据库时出错(可忽略):", error); - } -} - -// 数据库初始化 -async function initializeApp() { - try { - // 1. 清理旧数据库 - await cleanupOldDatabase(); - - // 2. 打开新数据库 - await db.open(); - console.log("数据库初始化成功"); - - // 3. 开发环境清空数据(可选) - if (process.env.NODE_ENV === "development") { - console.log("开发环境:跳过数据清理"); - // 如需清空数据,取消下面的注释 - // await db.chatSessions.clear(); - // await db.contactsUnified.clear(); - // await db.contactLabelMap.clear(); - // await db.userLoginRecords.clear(); - } - } catch (error) { - console.error("数据库初始化失败:", error); + console.warn("Failed to prepare database before app bootstrap:", error); } - // 渲染应用 const root = createRoot(document.getElementById("root")!); root.render( @@ -106,5 +27,4 @@ async function initializeApp() { ); } -// 启动应用 -initializeApp(); +void bootstrap(); diff --git a/Touchkebao/src/pages/pc/ckbox/api.ts b/Touchkebao/src/pages/pc/ckbox/api.ts index 1fe610860..f5717fa7d 100644 --- a/Touchkebao/src/pages/pc/ckbox/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/api.ts @@ -35,28 +35,64 @@ export function updateConfig(params) { return request2("/api/WechatFriend/updateConfig", params, "PUT"); } //获取聊天记录-2 获取列表 -export function getChatMessages(params: { - wechatAccountId: number; - wechatFriendId?: number; - wechatChatroomId?: number; - From: number; - To: number; - Count: number; - olderData: boolean; -}) { - return request2("/api/FriendMessage/SearchMessage", params, "GET"); +export interface messreocrParams { + From?: number | string; + To?: number | string; + /** + * 当前页码,从 1 开始 + */ + page?: number; + /** + * 每页条数 + */ + limit?: number; + /** + * 群id + */ + wechatChatroomId?: number | string; + /** + * 好友id + */ + wechatFriendId?: number | string; + /** + * 微信账号ID + */ + wechatAccountId?: number | string; + /** + * 关键词、类型等扩展参数 + */ + [property: string]: any; } -export function getChatroomMessages(params: { - wechatAccountId: number; - wechatFriendId?: number; - wechatChatroomId?: number; - From: number; - To: number; - Count: number; - olderData: boolean; -}) { - return request2("/api/ChatroomMessage/SearchMessage", params, "GET"); +export function getChatMessages(params: messreocrParams) { + return request("/v1/kefu/message/details", params, "GET"); } +export function getChatroomMessages(params: messreocrParams) { + return request("/v1/kefu/message/details", params, "GET"); +} +//=====================旧============================== + +// export function getChatMessages(params: { +// wechatAccountId: number; +// wechatFriendId?: number; +// wechatChatroomId?: number; +// From: number; +// To: number; +// Count: number; +// olderData: boolean; +// }) { +// return request2("/api/FriendMessage/SearchMessage", params, "GET"); +// } +// export function getChatroomMessages(params: { +// wechatAccountId: number; +// wechatFriendId?: number; +// wechatChatroomId?: number; +// From: number; +// To: number; +// Count: number; +// olderData: boolean; +// }) { +// return request2("/api/ChatroomMessage/SearchMessage", params, "GET"); +// } //获取群列表 export function getGroupList(params: { prevId: number; count: number }) { diff --git a/Touchkebao/src/pages/pc/ckbox/data.ts b/Touchkebao/src/pages/pc/ckbox/data.ts index 037a43678..9b8aa285a 100644 --- a/Touchkebao/src/pages/pc/ckbox/data.ts +++ b/Touchkebao/src/pages/pc/ckbox/data.ts @@ -146,7 +146,7 @@ export interface ContractData { labels: string[]; signature: string; accountId: number; - extendFields: null; + extendFields?: Record | null; city?: string; lastUpdateTime: string; isPassed: boolean; diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/components/PushTaskModal.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/components/PushTaskModal.tsx index 74cec1e1e..af9d73732 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/components/PushTaskModal.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/components/PushTaskModal.tsx @@ -105,7 +105,7 @@ const PushTaskModal: React.FC = ({ }; const getSubtitle = () => { - return "智能批量推送,AI智能话术改写"; + return "智能批量推送,AI智能话术改写"; }; // 步骤2的标题 diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx index d682b46c6..5ee5b4bdf 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/create-push-task/index.tsx @@ -127,7 +127,7 @@ const CreatePushTask: React.FC = () => { } }, [validPushType]); - const subtitle = "智能批量推送,AI智能话术改写"; + const subtitle = "智能批量推送,AI智能话术改写"; const step2Title = useMemo(() => { switch (validPushType) { diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/index.tsx index acf205f30..2a8559372 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/message-push-assistant/index.tsx @@ -12,7 +12,10 @@ import { } from "@ant-design/icons"; import styles from "./index.module.scss"; -export type PushType = "friend-message" | "group-message" | "group-announcement"; +export type PushType = + | "friend-message" + | "group-message" + | "group-announcement"; const MessagePushAssistant: React.FC = () => { const navigate = useNavigate(); @@ -26,7 +29,9 @@ const MessagePushAssistant: React.FC = () => { icon: , color: "#1890ff", onClick: () => { - navigate("/pc/powerCenter/message-push-assistant/create-push-task/friend-message"); + navigate( + "/pc/powerCenter/message-push-assistant/create-push-task/friend-message", + ); }, }, { @@ -36,17 +41,21 @@ const MessagePushAssistant: React.FC = () => { icon: , color: "#52c41a", onClick: () => { - navigate("/pc/powerCenter/message-push-assistant/create-push-task/group-message"); + navigate( + "/pc/powerCenter/message-push-assistant/create-push-task/group-message", + ); }, }, { id: "group-announcement", title: "群公告推送", - description: "向选定的微信群发布群公告", + description: "向选定的微信群批量发布群公告", icon: , color: "#722ed1", onClick: () => { - navigate("/pc/powerCenter/message-push-assistant/create-push-task/group-announcement"); + navigate( + "/pc/powerCenter/message-push-assistant/create-push-task/group-announcement", + ); }, }, ]; @@ -81,7 +90,7 @@ const MessagePushAssistant: React.FC = () => {
navigate("/pc/powerCenter")} diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts index dceeed011..a36270332 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/api.ts @@ -25,41 +25,13 @@ export interface GetPushHistoryResponse { /** * 获取推送历史列表 */ -export const getPushHistory = async ( - params: GetPushHistoryParams -): Promise => { - try { - // TODO: 替换为实际的API接口地址 - const response = await request.get("/api/push-history", { params }); - - // 如果接口返回的数据格式不同,需要在这里进行转换 - if (response.data && response.data.success !== undefined) { - return response.data; - } - - // 兼容不同的响应格式 - return { - success: true, - data: { - list: response.data?.list || response.data?.data || [], - total: response.data?.total || 0, - page: response.data?.page || params.page || 1, - pageSize: response.data?.pageSize || params.pageSize || 10, - }, - }; - } catch (error: any) { - console.error("获取推送历史失败:", error); - return { - success: false, - message: error?.message || "获取推送历史失败", - }; - } +export interface GetGroupPushHistoryParams { + keyword?: string; + limit: string; + page: string; + workbenchId?: string; + [property: string]: any; +} +export const getPushHistory = async (params: GetGroupPushHistoryParams) => { + return request("/v1/workbench/group-push-history", params, "GET"); }; - - - - - - - - diff --git a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx index 0697b99d3..bd2545a70 100644 --- a/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/powerCenter/push-history/index.tsx @@ -76,18 +76,31 @@ const PushHistory: React.FC = () => { } const response = await getPushHistory(params); + const result = response?.data ?? response ?? {}; - if (response.success) { - setDataSource(response.data?.list || []); - setPagination(prev => ({ - ...prev, - current: response.data?.page || page, - total: response.data?.total || 0, - })); - } else { - message.error(response.message || "获取推送历史失败"); + if (!result || typeof result !== "object") { + message.error("获取推送历史失败"); setDataSource([]); + return; } + + const toNumber = (value: unknown, fallback: number) => { + const parsed = Number(value); + return Number.isFinite(parsed) ? parsed : fallback; + }; + + const list = Array.isArray(result.list) ? result.list : []; + const total = toNumber(result.total, pagination.total); + const currentPage = toNumber(result.page, page); + const pageSize = toNumber(result.pageSize, pagination.pageSize); + + setDataSource(list); + setPagination(prev => ({ + ...prev, + current: currentPage, + pageSize, + total, + })); } catch (error) { console.error("获取推送历史失败:", error); message.error("获取推送历史失败,请稍后重试"); @@ -211,9 +224,7 @@ const PushHistory: React.FC = () => { dataIndex: "pushContent", key: "pushContent", ellipsis: true, - render: (text: string) => ( - {text} - ), + render: (text: string) => {text}, }, { title: "目标数量", @@ -287,7 +298,9 @@ const PushHistory: React.FC = () => { subtitle="查看所有推送任务的历史记录" showBackButton={true} backButtonText="返回" - onBackClick={() => navigate("/pc/powerCenter/message-push-assistant")} + onBackClick={() => + navigate("/pc/powerCenter/message-push-assistant") + } />
} @@ -369,11 +382,3 @@ const PushHistory: React.FC = () => { }; export default PushHistory; - - - - - - - - diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/api.ts b/Touchkebao/src/pages/pc/ckbox/weChat/api.ts index 8ce1bcacc..32bb96047 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/api.ts +++ b/Touchkebao/src/pages/pc/ckbox/weChat/api.ts @@ -94,7 +94,7 @@ export function WechatFriendAllot(params: { //获取可转移客服列表 export function getTransferableAgentList() { - return request2("/api/account/myDepartmentAccountsForTransfer", {}, "GET"); + return request("/v1/kefu/accounts/list", {}, "GET"); } // 微信好友列表 diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx index 1c7975f51..1b4d86f77 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/components/toContract/index.tsx @@ -9,7 +9,6 @@ import { import { useCurrentContact } from "@/store/module/weChat/weChat"; import { ContactManager } from "@/utils/dbAction/contact"; import { MessageManager } from "@/utils/dbAction/message"; -import { triggerRefresh } from "@/store/module/weChat/message"; import { useUserStore } from "@/store/module/user"; import { useWeChatStore } from "@/store/module/weChat/weChat"; const { TextArea } = Input; @@ -49,7 +48,7 @@ const ToContract: React.FC = ({ const openModal = () => { setVisible(true); getTransferableAgentList().then(data => { - setCustomerServiceList(data); + setCustomerServiceList(data.list); }); }; @@ -110,10 +109,7 @@ const ToContract: React.FC = ({ await ContactManager.deleteContact(currentContact.id); console.log("✅ 已从联系人数据库删除"); - // 3. 触发会话列表刷新 - triggerRefresh(); - - // 4. 清空当前选中的联系人(关闭聊天窗口) + // 3. 清空当前选中的联系人(关闭聊天窗口) clearCurrentContact(); message.success("转接成功,已清理本地数据"); @@ -167,10 +163,7 @@ const ToContract: React.FC = ({ await ContactManager.deleteContact(currentContact.id); console.log("✅ 已从联系人数据库删除"); - // 3. 触发会话列表刷新 - triggerRefresh(); - - // 4. 清空当前选中的联系人(关闭聊天窗口) + // 3. 清空当前选中的联系人(关闭聊天窗口) clearCurrentContact(); message.success("转回成功,已清理本地数据"); diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx index 8ae096e81..864ccb6e9 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageEnter/index.tsx @@ -167,27 +167,11 @@ const MessageEnter: React.FC = ({ contract }) => { // AI 消息处理 useEffect(() => { if (quoteMessageContent) { - console.log( - "🤖 AI消息到达 - aiQuoteMessageContent:", - aiQuoteMessageContent, - ); - - // 检查:如果用户输入框已有内容(且不是之前的AI内容),不覆盖 - if (inputValue && inputValue !== quoteMessageContent) { - console.log("⚠️ 用户正在输入,不覆盖输入内容"); - updateQuoteMessageContent(""); // 清空AI回复 - return; - } - if (isAiAssist) { - // AI辅助模式:填充到输入框,等待人工确认 - console.log("✨ AI辅助模式:填充消息到输入框"); setInputValue(quoteMessageContent); } if (isAiTakeover) { - // AI接管模式:直接发送消息(传入内容,避免 state 闭包问题) - console.log("🚀 AI接管模式:自动发送消息"); handleSend(quoteMessageContent); } } diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx index 1da7da52a..4bfe7c513 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/MessageRecord/index.tsx @@ -145,6 +145,9 @@ const MessageRecord: React.FC = ({ contract }) => { const [selectedRecords, setSelectedRecords] = useState([]); const currentMessages = useWeChatStore(state => state.currentMessages); + const currentMessagesHasMore = useWeChatStore( + state => state.currentMessagesHasMore, + ); const loadChatMessages = useWeChatStore(state => state.loadChatMessages); const messagesLoading = useWeChatStore(state => state.messagesLoading); @@ -552,8 +555,14 @@ const MessageRecord: React.FC = ({ contract }) => { }; // 用于分组消息并添加时间戳的辅助函数 - const groupMessagesByTime = (messages: ChatRecord[]) => { - return messages + const groupMessagesByTime = (messages: ChatRecord[] | null | undefined) => { + const safeMessages = Array.isArray(messages) + ? messages + : Array.isArray((messages as any)?.list) + ? ((messages as any).list as ChatRecord[]) + : []; + + return safeMessages .filter(msg => msg !== null && msg !== undefined) // 过滤掉null和undefined的消息 .map(msg => ({ time: formatWechatTime(String(msg?.wechatTime)), @@ -639,7 +648,7 @@ const MessageRecord: React.FC = ({ contract }) => { )} - {isOwn && ( + {!!isOwn && ( <> {/* Checkbox 显示控制 */} {showCheckbox && ( @@ -650,7 +659,6 @@ const MessageRecord: React.FC = ({ contract }) => { /> )} - = ({ contract }) => { ); }; const loadMoreMessages = () => { - // 兼容性处理:检查消息数组和时间戳 - if (!currentMessages || currentMessages.length === 0) { - console.warn("No messages available for loading more"); + if (messagesLoading || !currentMessagesHasMore) { return; } - - const firstMessage = currentMessages[0]; - if (!firstMessage || !firstMessage.createTime) { - console.warn("Invalid message or createTime"); - return; - } - - // 兼容性处理:确保时间戳格式正确 - let timestamp; - try { - const date = new Date(firstMessage.createTime); - if (isNaN(date.getTime())) { - console.warn("Invalid createTime format:", firstMessage.createTime); - return; - } - timestamp = date.getTime() - 24 * 36000 * 1000; - } catch (error) { - console.error("Error parsing createTime:", error); - return; - } - - loadChatMessages(false, timestamp); + loadChatMessages(false); }; const handleForwardMessage = (messageData: ChatRecord) => { @@ -785,8 +770,17 @@ const MessageRecord: React.FC = ({ contract }) => { return (
-
loadMoreMessages()}> - 点击加载更早的信息 {messagesLoading ? : ""} +
loadMoreMessages()} + style={{ + cursor: + currentMessagesHasMore && !messagesLoading ? "pointer" : "default", + opacity: currentMessagesHasMore ? 1 : 0.6, + }} + > + {currentMessagesHasMore ? "点击加载更早的信息" : "已经没有更早的消息了"} + {messagesLoading ? : ""}
{groupMessagesByTime(currentMessages).map((group, groupIndex) => ( diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/Person.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/Person.module.scss index 66cf4b59e..add242f87 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/Person.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/Person.module.scss @@ -4,3 +4,51 @@ height: 100%; overflow-y: auto; } + +.tabHeader { + display: flex; + align-items: center; + padding: 0 30px; + border-bottom: 1px solid #f0f0f0; + min-height: 48px; +} + +.tabItem { + position: relative; + display: inline-flex; + align-items: center; + justify-content: center; + margin-right: 24px; + padding: 12px 0; + font-size: 14px; + color: #333; + cursor: pointer; + transition: color 0.2s ease; +} + +.tabItem:last-child { + margin-right: 0; +} + +.tabItem:hover { + color: #1677ff; +} + +.tabItemActive { + color: #1677ff; + font-weight: 500; +} + +.tabUnderline { + position: absolute; + left: 0; + right: 0; + bottom: 0; + height: 2px; + background: transparent; + transition: background 0.2s ease; +} + +.tabItemActive .tabUnderline { + background: #1677ff; +} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/Person.module.scss b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/Person.module.scss index 0a061cc43..89005006b 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/Person.module.scss +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/Person.module.scss @@ -197,6 +197,12 @@ } } +.footerActions { + display: flex; + justify-content: flex-end; + margin-top: 16px; +} + // 响应式设计 @media (max-width: 768px) { .profileSider { diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/api.ts b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/api.ts new file mode 100644 index 000000000..2f2ea0dce --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/api.ts @@ -0,0 +1,88 @@ +import request from "@/api/request"; +// 更新好友信息 +export interface UpdateFriendInfoParams { + id: number; + phone: string; + company: string; + name: string; + position: string; + email: string; + address: string; + qq: string; + remark: string; +} +export function updateFriendInfo(params: UpdateFriendInfoParams): Promise { + return request("/v1/kefu/wechatFriend/updateInfo", params, "POST"); +} + +// 更新本地数据库中的好友信息 +export interface UpdateLocalDBParams { + wechatFriendId: number; + extendFields: string; + updateConversation?: boolean; // 是否同时更新会话列表 +} + +export function updateLocalDBFriendInfo( + params: UpdateLocalDBParams, +): Promise { + return request("/v1/kefu/wechatFriend/updateLocalDB", params, "POST"); +} +// 获取好友信息 +export interface GetFriendInfoParams { + id: number; +} + +export interface FriendDetailResponse { + detail: { + id: number; + wechatAccountId: number; + alias: string; + wechatId: string; + conRemark: string; + nickname: string; + pyInitial: string; + quanPin: string; + avatar: string; + gender: number; + region: string; + addFrom: number; + labels: any[]; + siteLabels: string[]; + signature: string; + isDeleted: number; + isPassed: number; + deleteTime: number; + accountId: number; + extendFields: string; + accountUserName: string; + accountRealName: string; + accountNickname: string; + ownerAlias: string; + ownerWechatId: string; + ownerNickname: string; + ownerAvatar: string; + phone: string; + thirdParty: string; + groupId: number; + passTime: string; + additionalPicture: string; + desc: string; + country: string; + privince: string; + city: string; + createTime: string; + updateTime: string; + R: string; + F: string; + M: string; + realName: null | string; + company: null | string; + position: null | string; + aiType: number; + }; +} +export function getFriendInfo( + params: GetFriendInfoParams, +): Promise { + return request("/v1/kefu/wechatFriend/detail", params, "GET"); +} diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx new file mode 100644 index 000000000..4966a8631 --- /dev/null +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/components/detailValue.tsx @@ -0,0 +1,309 @@ +import React, { useCallback, useState, useEffect } from "react"; +import { Input, message } from "antd"; +import { Button } from "antd-mobile"; +import { EditOutlined } from "@ant-design/icons"; +import { updateFriendInfo, UpdateFriendInfoParams } from "../api"; + +import styles from "../Person.module.scss"; + +export interface DetailValueField { + label: string; + key: string; + ifEdit?: boolean; + placeholder?: string; + type?: "text" | "textarea"; + editable?: boolean; +} + +export interface DetailValueProps { + fields: DetailValueField[]; + value?: Record; + onChange?: (next: Record) => void; + onSubmit?: (next: Record, changedKeys: string[]) => void; + submitText?: string; + submitting?: boolean; + renderFooter?: React.ReactNode; + saveHandler?: ( + values: Record, + changedKeys: string[], + ) => Promise; + onSaveSuccess?: ( + values: Record, + changedKeys: string[], + ) => void; + isGroup?: boolean; +} + +const DetailValue: React.FC = ({ + fields, + value = {}, + onChange, + onSubmit, + submitText = "保存", + submitting = false, + renderFooter, + saveHandler, + onSaveSuccess, + isGroup = false, +}) => { + const [messageApi, contextHolder] = message.useMessage(); + const [editingFields, setEditingFields] = useState>( + {}, + ); + const [fieldValues, setFieldValues] = useState>(value); + + const [originalValues, setOriginalValues] = + useState>(value); + const [changedKeys, setChangedKeys] = useState([]); + + // 当外部value变化时,更新内部状态 + useEffect(() => { + setFieldValues(value); + setOriginalValues(value); + setChangedKeys([]); + // 重置所有编辑状态 + const newEditingFields: Record = {}; + fields.forEach(field => { + newEditingFields[field.key] = false; + }); + setEditingFields(newEditingFields); + }, [value, fields]); + + const handleFieldChange = useCallback( + (fieldKey: string, nextVal: string) => { + setFieldValues(prev => ({ + ...prev, + [fieldKey]: nextVal, + })); + + // 检查值是否发生变化,更新changedKeys + if (nextVal !== originalValues[fieldKey]) { + if (!changedKeys.includes(fieldKey)) { + setChangedKeys(prev => [...prev, fieldKey]); + } + } else { + // 如果值恢复到原始值,从changedKeys中移除 + setChangedKeys(prev => prev.filter(key => key !== fieldKey)); + } + + // 调用外部onChange,但不触发自动保存 + if (onChange) { + onChange({ + ...fieldValues, + [fieldKey]: nextVal, + }); + } + }, + [onChange, fieldValues, originalValues, changedKeys], + ); + + const handleEditField = useCallback((fieldKey: string) => { + setEditingFields(prev => ({ + ...prev, + [fieldKey]: true, + })); + }, []); + + const handleCancelEdit = useCallback( + (fieldKey: string) => { + // 恢复原始值 + setFieldValues(prev => ({ + ...prev, + [fieldKey]: originalValues[fieldKey] || "", + })); + + // 从changedKeys中移除 + setChangedKeys(prev => prev.filter(key => key !== fieldKey)); + + // 关闭编辑状态 + setEditingFields(prev => ({ + ...prev, + [fieldKey]: false, + })); + }, + [originalValues], + ); + + const handleSubmit = useCallback(async () => { + if (changedKeys.length === 0) { + messageApi.info("没有需要保存的更改"); + return; + } + + try { + if (isGroup) { + // 群组信息使用传入的saveHandler + if (saveHandler) { + await saveHandler(fieldValues, changedKeys); + } else { + onSubmit?.(fieldValues, changedKeys); + } + } else { + // 个人资料信息处理 + if (changedKeys.includes("conRemark")) { + // 微信备注是特例,使用WebSocket更新 + if (saveHandler) { + await saveHandler(fieldValues, changedKeys); + } else { + onSubmit?.(fieldValues, changedKeys); + } + } else { + // 其他个人资料信息使用updateFriendInfo API + const params: UpdateFriendInfoParams = { + id: Number(value.id) || 0, + phone: fieldValues.phone || "", + company: fieldValues.company || "", + name: fieldValues.name || "", + position: fieldValues.position || "", + email: fieldValues.email || "", + address: fieldValues.address || "", + qq: fieldValues.qq || "", + remark: fieldValues.remark || "", + }; + await updateFriendInfo(params); + } + } + + // 更新原始值 + setOriginalValues(fieldValues); + // 清空changedKeys + setChangedKeys([]); + // 关闭所有编辑状态 + const newEditingFields: Record = {}; + fields.forEach(field => { + newEditingFields[field.key] = false; + }); + setEditingFields(newEditingFields); + // 调用保存成功回调 + onSaveSuccess?.(fieldValues, changedKeys); + messageApi.success("保存成功"); + } catch (error) { + messageApi.error("保存失败"); + console.error("保存失败:", error); + } + }, [ + onSubmit, + saveHandler, + onSaveSuccess, + fieldValues, + changedKeys, + fields, + messageApi, + isGroup, + value.id, + ]); + + const isEditing = Object.values(editingFields).some(Boolean); + + return ( +
+ {contextHolder} + {fields.map(field => { + const disabled = field.ifEdit === false; + const fieldValue = fieldValues[field.key] ?? ""; + const isFieldEditing = editingFields[field.key]; + const InputComponent = + field.type === "textarea" ? Input.TextArea : Input; + + return ( +
+ {field.label}: +
+ {disabled ? ( + {fieldValue || field.placeholder || ""} + ) : isFieldEditing ? ( +
+ + handleFieldChange(field.key, event.target.value) + } + onPressEnter={undefined} + autoFocus + rows={field.type === "textarea" ? 4 : undefined} + /> +
+ + +
+
+ ) : ( +
handleEditField(field.key)} + onMouseEnter={e => { + e.currentTarget.style.backgroundColor = "#f5f5f5"; + e.currentTarget.style.borderColor = "#d9d9d9"; + }} + onMouseLeave={e => { + e.currentTarget.style.backgroundColor = "transparent"; + e.currentTarget.style.borderColor = "transparent"; + }} + > + + {fieldValue || field.placeholder || ""} + + +
+ )} +
+
+ ); + })} + + {(onSubmit || renderFooter) && !isEditing && changedKeys.length > 0 && ( +
+ {renderFooter} + +
+ )} +
+ ); +}; + +export default DetailValue; diff --git a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx index 9e06f5939..6f40e175e 100644 --- a/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx +++ b/Touchkebao/src/pages/pc/ckbox/weChat/components/ChatWindow/components/ProfileCard/components/ProfileModules/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useMemo } from "react"; import { Input, Button, @@ -9,11 +9,9 @@ import { message, Modal, } from "antd"; +// 不再需要导入updateFriendInfo,因为已经在detailValue.tsx中使用 import { - PhoneOutlined, UserOutlined, - TeamOutlined, - EnvironmentOutlined, CloseOutlined, EditOutlined, CheckOutlined, @@ -22,14 +20,20 @@ import { SwapOutlined, } from "@ant-design/icons"; import { ContractData, weChatGroup } from "@/pages/pc/ckbox/data"; -import { useCkChatStore } from "@/store/module/ckchat/ckchat"; +import { useCustomerStore } from "@/store/module/weChat/customer"; import { useWebSocketStore } from "@/store/module/websocket/websocket"; import { useWeChatStore } from "@/store/module/weChat/weChat"; -import { useContactStore } from "@/store/module/weChat/contacts"; +import { contactUnifiedService } from "@/utils/db"; import { generateAiText } from "@/api/ai"; import TwoColumnSelection from "@/components/TwoColumnSelection/TwoColumnSelection"; import TwoColumnMemberSelection from "@/components/MemberSelection/TwoColumnMemberSelection"; import { FriendSelectionItem } from "@/components/FriendSelection/data"; +import DetailValue from "./components/detailValue"; +import { + getFriendInfo, + FriendDetailResponse, + updateLocalDBFriendInfo, +} from "./api"; import styles from "./Person.module.scss"; interface PersonProps { contract: ContractData | weChatGroup; @@ -37,7 +41,6 @@ interface PersonProps { const Person: React.FC = ({ contract }) => { const [messageApi, contextHolder] = message.useMessage(); - const [isEditingRemark, setIsEditingRemark] = useState(false); const [remarkValue, setRemarkValue] = useState(contract.conRemark || ""); const [selectedTags, setSelectedTags] = useState( contract.labels || [], @@ -45,18 +48,18 @@ const Person: React.FC = ({ contract }) => { const [allAvailableTags, setAllAvailableTags] = useState([]); const [isAddingTag, setIsAddingTag] = useState(false); const [newTagValue, setNewTagValue] = useState(""); + const [friendDetail, setFriendDetail] = useState( + null, + ); // 判断是否为群聊 const isGroup = "chatroomId" in contract; // 群聊相关状态 - const [isEditingGroupName, setIsEditingGroupName] = useState(false); const [groupNameValue, setGroupNameValue] = useState(contract.name || ""); const [groupNoticeValue, setGroupNoticeValue] = useState( contract.notice || "", ); - const [isEditingSelfDisplayName, setIsEditingSelfDisplayName] = - useState(false); const [selfDisplayNameValue, setSelfDisplayNameValue] = useState( contract.selfDisplyName || "", ); @@ -97,9 +100,7 @@ const Person: React.FC = ({ contract }) => { useState(false); const [isTransferOwnerSelectionVisible, setIsTransferOwnerSelectionVisible] = useState(false); - const [selectedFriends, setSelectedFriends] = useState( - [], - ); + const [contractList, setContractList] = useState([]); const handleAddFriend = member => { @@ -209,11 +210,16 @@ const Person: React.FC = ({ contract }) => { // 构建联系人或群聊详细信息 - const kfSelectedUser = useCkChatStore(state => - state.getKfUserInfo(contract.wechatAccountId || 0), - ); + const customerList = useCustomerStore(state => state.customerList); + const kfSelectedUser = useMemo(() => { + if (!contract.wechatAccountId) return null; + const matchedCustomer = customerList.find( + customer => customer.id === contract.wechatAccountId, + ); + return matchedCustomer || null; + }, [customerList, contract.wechatAccountId]); - const { getContactsByCustomer } = useContactStore(); + // 不再需要从useContactStore获取getContactsByCustomer const { sendCommand } = useWebSocketStore(); @@ -221,16 +227,12 @@ const Person: React.FC = ({ contract }) => { const hasGroupManagePermission = () => { // 暂时给所有用户完整的群管理权限 return true; - // if (!kfSelectedUser || !contract) return false; - // // 当客服的wechatId与contract的chatroomOwner相同时,才有完整的群管理权限 - // return kfSelectedUser.nickname === (contract as any).chatroomOwnerNickname; }; // 获取所有可用标签 useEffect(() => { const fetchAvailableTags = async () => { try { - // 从kfSelectedUser.labels和contract.labels合并获取所有标签 const kfTags = kfSelectedUser?.labels || []; const contractTags = contract.labels || []; const allTags = [...new Set([...kfTags, ...contractTags])]; @@ -242,76 +244,204 @@ const Person: React.FC = ({ contract }) => { fetchAvailableTags(); }, [kfSelectedUser, contract.labels]); + // 获取好友详细信息 - 静默请求,成功时更新数据,失败时不做任何处理 + const fetchFriendDetail = React.useCallback(async () => { + if (isGroup) return; // 群聊不需要获取好友详情 + + try { + // 静默请求,不显示加载状态 + const response = await getFriendInfo({ id: contract.id }); + // 请求成功时更新数据 + setFriendDetail(response); + + // 解析扩展字段 + try { + const extendFieldsObj = JSON.parse( + response.detail.extendFields || "{}", + ); + setExtendFields(extendFieldsObj); + } catch (e) { + console.error("Failed to parse extendFields:", e); + // 解析失败时不更新状态,保持原有数据 + } + } catch (err) { + // 请求失败时静默处理,只记录日志,不更新UI状态 + console.error("获取好友详情失败:", err); + } + }, [contract.id, isGroup]); + + // 当contract变化时在后台静默获取好友详情 + useEffect(() => { + if (!isGroup && contract.id) { + // 使用setTimeout将请求移至下一个事件循环,确保UI先渲染 + setTimeout(() => { + fetchFriendDetail(); + }, 0); + } + }, [contract.id, isGroup, fetchFriendDetail]); + // 当contract变化时更新各种值 useEffect(() => { setRemarkValue(contract.conRemark || ""); - setIsEditingRemark(false); setSelectedTags(contract.labels || []); + try { + // 确保extendFields是最新的值 + const extFieldsObj = + typeof contract.extendFields === "string" + ? JSON.parse(contract.extendFields || "{}") + : contract.extendFields || {}; + setExtendFields(extFieldsObj); + } catch (e) { + console.error("Failed to parse extendFields in useEffect:", e); + setExtendFields({}); + } if (isGroup) { setGroupNameValue(contract.name || ""); - setIsEditingGroupName(false); setGroupNoticeValue(contract.notice || ""); setSelfDisplayNameValue(contract.selfDisplyName || ""); - setIsEditingSelfDisplayName(false); } + + // 不再需要在这里触发获取好友详情,已在单独的useEffect中处理 }, [ contract.conRemark, contract.labels, contract.name, contract.notice, contract.selfDisplyName, + contract.extendFields, + contract.id, isGroup, + fetchFriendDetail, ]); // 处理备注保存 - const handleSaveRemark = () => { - if (isGroup) { - // 群聊备注修改 - sendCommand("CmdModifyGroupRemark", { - wechatAccountId: contract.wechatAccountId, - chatroomId: contract.chatroomId, - newRemark: remarkValue, - }); - } else { - // 好友备注修改 - sendCommand("CmdModifyFriendRemark", { - wechatAccountId: contract.wechatAccountId, - wechatFriendId: contract.id, - newRemark: remarkValue, - }); + const handleSaveRemark = async ( + values: Record, + changedKeys: string[], + ) => { + // 构建更新后的扩展字段 + const updatedExtendFields = { ...extendFields }; + + // 更新各个扩展字段 + const extendFieldKeys = [ + "phone", + "company", + "position", + "email", + "address", + "qq", + "remark", + ]; + extendFieldKeys.forEach(key => { + if (changedKeys.includes(key) && values[key] !== undefined) { + updatedExtendFields[key] = values[key]; + } + }); + + const extendFieldsStr = JSON.stringify(updatedExtendFields || {}); + + // 更新remarkValue + if (changedKeys.includes("conRemark")) { + setRemarkValue(values.conRemark); } - messageApi.success("备注保存成功"); - setIsEditingRemark(false); - // 更新contract对象中的备注(实际项目中应该通过props回调或状态管理) + // 更新所有扩展字段 + setExtendFields(updatedExtendFields); + + // 更新父组件中的contract副本,确保切换tab后数据不会丢失 + if (contract && typeof contract === "object") { + // 更新contract的extendFields字段 + contract.extendFields = extendFieldsStr; + + // 如果有备注变更,同时更新备注 + if (changedKeys.includes("conRemark")) { + contract.conRemark = values.conRemark; + } + } + + try { + // 仅使用WebSocket命令同步备注信息 + if ( + changedKeys.includes("conRemark") || + changedKeys.some(key => extendFieldKeys.includes(key)) + ) { + if (isGroup) { + // 群聊备注修改 + sendCommand("CmdModifyGroupRemark", { + wechatAccountId: contract.wechatAccountId, + chatroomId: contract.chatroomId, + newRemark: values.conRemark, + extendFields: extendFieldsStr, + }); + } else { + // 好友备注修改 + sendCommand("CmdModifyFriendRemark", { + wechatAccountId: contract.wechatAccountId, + wechatFriendId: contract.id, + newRemark: values.conRemark, + extendFields: extendFieldsStr, + }); + + // 同时更新会话列表和好友本地数据库中的extendFields字段 + updateLocalDBFriendInfo({ + wechatFriendId: contract.id, + extendFields: extendFieldsStr, + updateConversation: true, // 同时更新会话列表 + }).catch(err => { + console.error("更新本地数据库失败:", err); + }); + } + } + + // 如果有API返回的详情数据,更新本地状态 + if (friendDetail && !isGroup) { + setFriendDetail(prev => { + if (!prev) return prev; + return { + ...prev, + detail: { + ...prev.detail, + conRemark: changedKeys.includes("conRemark") + ? values.conRemark + : prev.detail.conRemark, + extendFields: extendFieldsStr, + }, + }; + }); + } + } catch (error) { + console.error("保存好友信息失败:", error); + return Promise.reject(error); + } + + // 返回Promise以便DetailValue组件处理成功状态 + return Promise.resolve(); }; // 处理群名称保存 - const handleSaveGroupName = () => { + const handleSaveGroupName = async ( + values: Record, + changedKeys: string[], + ) => { if (!hasGroupManagePermission()) { messageApi.error("只有群主才能修改群名称"); - return; + return Promise.reject("没有权限"); } + + // 更新groupNameValue + if (changedKeys.includes("groupName")) { + setGroupNameValue(values.groupName); + } + sendCommand("CmdChatroomOperate", { wechatAccountId: contract.wechatAccountId, wechatChatroomId: contract.id, chatroomOperateType: 6, - extra: `{"chatroomName":"${groupNameValue}"}`, + extra: `{"chatroomName":"${values.groupName}"}`, }); - messageApi.success("群名称修改成功"); - setIsEditingGroupName(false); - }; - - // 点击编辑群名称按钮 - const handleEditGroupName = () => { - if (!hasGroupManagePermission()) { - messageApi.error("只有群主才能修改群名称"); - return; - } - setGroupNameValue(contractInfo.name || ""); - setIsEditingGroupName(true); + return Promise.resolve(); }; // 处理群公告保存 @@ -373,40 +503,27 @@ const Person: React.FC = ({ contract }) => { messageApi.success("已应用AI生成的群公告内容"); }; - // 点击编辑群公告按钮 - const handleEditGroupNotice = () => { - if (!hasGroupManagePermission()) { - messageApi.error("只有群主才能修改群公告"); - return; - } - setGroupNoticeValue(contract.notice || ""); - setIsGroupNoticeModalVisible(true); - }; - // 处理我在本群中的昵称保存 - const handleSaveSelfDisplayName = () => { + const handleSaveSelfDisplayName = async ( + values: Record, + changedKeys: string[], + ) => { + // 更新selfDisplayNameValue + if (changedKeys.includes("selfDisplayName")) { + setSelfDisplayNameValue(values.selfDisplayName); + } + sendCommand("CmdChatroomOperate", { wechatAccountId: contract.wechatAccountId, wechatChatroomId: contract.id, chatroomOperateType: 8, - extra: `${selfDisplayNameValue}`, + extra: `${values.selfDisplayName}`, }); - messageApi.success("群昵称修改成功"); - setIsEditingSelfDisplayName(false); + return Promise.resolve(); }; - // 点击编辑群昵称按钮 - const handleEditSelfDisplayName = () => { - setSelfDisplayNameValue(contract.selfDisplyName || ""); - setIsEditingSelfDisplayName(true); - }; - - // 处理取消编辑 - const handleCancelEdit = () => { - setRemarkValue(contract.conRemark || ""); - setIsEditingRemark(false); - }; + // 这里不再需要handleCancelEdit,已由DetailValue组件内部处理 // 处理标签点击切换 const handleTagToggle = (tagName: string) => { @@ -507,31 +624,206 @@ const Person: React.FC = ({ contract }) => { }, }); }; - + const [extendFields, setExtendFields] = useState(() => { + try { + return JSON.parse(contract.extendFields || "{}"); + } catch (e) { + console.error("Failed to parse extendFields:", e); + return {}; + } + }); // 构建联系人或群聊详细信息 - const contractInfo = { - name: contract.name || contract.nickname, - nickname: contract.nickname, - conRemark: remarkValue, // 使用当前编辑的备注值 - alias: contract.alias, - wechatId: contract.wechatId, - chatroomId: isGroup ? contract.chatroomId : undefined, - chatroomOwner: isGroup ? contract.chatroomOwner : undefined, - avatar: contract.avatar || contract.chatroomAvatar, - phone: contract.phone || "-", - email: contract.email || "-", - department: contract.department || "-", - position: contract.position || "-", - company: contract.company || "-", - region: contract.region || "-", - joinDate: contract.joinDate || "-", - notice: isGroup ? contract.notice : undefined, - selfDisplyName: isGroup ? contract.selfDisplyName : undefined, - status: "在线", - tags: selectedTags, - bio: contract.bio || contract.signature || "-", + const contractInfo = useMemo(() => { + // 如果是个人资料且有API返回的详情数据,优先使用API数据 + if (!isGroup && friendDetail?.detail) { + const detail = friendDetail.detail; + // 解析扩展字段 + let extendFieldsObj: Record = {}; + try { + extendFieldsObj = JSON.parse(detail.extendFields || "{}"); + } catch (e) { + console.error("Failed to parse extendFields in contractInfo:", e); + } + + return { + name: detail.nickname || detail.alias, + nickname: detail.nickname, + alias: detail.alias, + wechatId: detail.wechatId, + avatar: detail.avatar, + phone: extendFieldsObj.phone || detail.phone || "", + conRemark: detail.conRemark || remarkValue, + remark: extendFieldsObj.remark || "", + email: extendFieldsObj.email || "", + department: detail.company || "", // 使用company作为department + position: extendFieldsObj.position || detail.position || "", + company: extendFieldsObj.company || detail.company || "", + region: detail.region || "", + joinDate: detail.createTime || "", // 使用createTime作为joinDate + status: "在线", + tags: detail.labels || selectedTags, + bio: detail.signature || "", + address: extendFieldsObj.address || "", + qq: extendFieldsObj.qq || "", + }; + } + + // 否则使用传入的contract数据 + return { + name: contract.name || contract.nickname, + nickname: contract.nickname, + alias: contract.alias, + wechatId: contract.wechatId, + chatroomId: isGroup ? contract.chatroomId : undefined, + chatroomOwner: isGroup ? contract.chatroomOwner : undefined, + avatar: contract.avatar || contract.chatroomAvatar, + phone: contract.phone || "", + conRemark: remarkValue, // 使用当前编辑的备注值 + remark: extendFields?.remark || "", + email: contract.email || "", + department: contract.department || "", + position: contract.position || "", + company: contract.company || "", + region: contract.region || "", + joinDate: contract.joinDate || "", + notice: isGroup ? contract.notice : undefined, + selfDisplyName: isGroup ? contract.selfDisplyName : undefined, + status: "在线", + tags: selectedTags, + bio: contract.bio || contract.signature || "", + address: contract.address || "", + qq: contract.qq || "", + }; + }, [ + contract, + friendDetail, + isGroup, + remarkValue, + selectedTags, + extendFields, + ]); + + // 分页状态 + const [currentContactPage, setCurrentContactPage] = useState(1); + const [contactPageSize] = useState(10); + const [isLoadingContacts, setIsLoadingContacts] = useState(false); + + // 从数据库获取联系人数据的通用函数 + const fetchContacts = async (page = 1) => { + try { + const { databaseManager, initializeDatabaseFromPersistedUser } = + await import("@/utils/db"); + + // 检查数据库初始化状态 + if (!databaseManager.isInitialized()) { + await initializeDatabaseFromPersistedUser(); + } + + // 获取当前用户ID + const userId = kfSelectedUser?.userId || 0; + const storeUserId = databaseManager.getCurrentUserId(); + const effectiveUserId = storeUserId || userId; + + if (!effectiveUserId) { + messageApi.error("无法获取用户信息,请尝试重新登录"); + return []; + } + + // 查询联系人数据 + const allContacts = await contactUnifiedService.findWhereMultiple([ + { field: "userId", operator: "equals", value: effectiveUserId }, + { + field: "wechatAccountId", + operator: "equals", + value: contract.wechatAccountId, + }, + { field: "type", operator: "equals", value: "friend" }, + ]); + + // 手动分页 + const startIndex = (page - 1) * contactPageSize; + const endIndex = startIndex + contactPageSize; + return allContacts.slice(startIndex, endIndex); + } catch (error) { + console.error("获取联系人数据失败:", error); + messageApi.error("获取联系人数据失败"); + return []; + } }; + const addMember = async () => { + try { + setIsLoadingContacts(true); + const pagedContacts = await fetchContacts(currentContactPage); + // 转换为选择器需要的数据格式 + const friendSelectionData = pagedContacts.map(item => ({ + id: item.id || item.serverId, + wechatId: item.wechatId, + nickname: item.nickname, + avatar: item.avatar || "", + conRemark: item.conRemark, + name: item.conRemark || item.nickname, // 用于搜索显示 + })); + + setContractList(friendSelectionData); + setIsFriendSelectionVisible(true); + + // 如果没有联系人数据,显示提示 + if (friendSelectionData.length === 0) { + messageApi.info("未找到可添加的联系人,可能需要先同步联系人数据"); + } + } catch (error) { + console.error("获取联系人列表失败:", error); + messageApi.error("获取联系人列表失败"); + } finally { + setIsLoadingContacts(false); + } + }; + + // 加载更多联系人 + const loadMoreContacts = async () => { + if (isLoadingContacts) return; + try { + setIsLoadingContacts(true); + const nextPage = currentContactPage + 1; + setCurrentContactPage(nextPage); + // 使用通用函数获取下一页联系人数据 + const pagedContacts = await fetchContacts(nextPage); + // 转换数据格式 + const newFriendSelectionData = pagedContacts.map(item => ({ + id: item.id || item.serverId, + wechatId: item.wechatId, + nickname: item.nickname, + avatar: item.avatar || "", + conRemark: item.conRemark, + name: item.conRemark || item.nickname, + })); + + // 更新列表并去重 + setContractList(prev => { + const newList = [...prev, ...newFriendSelectionData]; + // 确保列表中没有重复项 + const uniqueMap = new Map(); + const uniqueList = newList.filter(item => { + if (uniqueMap.has(item.id)) { + return false; + } + uniqueMap.set(item.id, true); + return true; + }); + return uniqueList; + }); + + messageApi.success(`已加载${pagedContacts.length}条联系人数据`); + } catch (error) { + console.error("加载更多联系人失败:", error); + messageApi.error("加载更多联系人失败"); + } finally { + setIsLoadingContacts(false); + } + }; + // 不再需要加载状态和错误状态的渲染,始终显示缓存数据 + return ( <> {contextHolder} @@ -562,226 +854,144 @@ const Person: React.FC = ({ contract }) => { {isGroup ? ( // 群聊信息 - <> -
- - 群名称: -
- {isEditingGroupName ? ( -
- setGroupNameValue(e.target.value)} - placeholder="请输入群名称" - size="small" - style={{ flex: 1 }} - /> -
- ) : ( -
- -

- {contractInfo.nickname || contractInfo.name} -

-
- {hasGroupManagePermission() && ( -
- )} -
-
-
- - 群ID: - - {contractInfo.chatroomId} - -
-
- - 群主: - - {contractInfo.chatroomOwner} - -
-
- - 群昵称: -
- {isEditingSelfDisplayName ? ( -
- setSelfDisplayNameValue(e.target.value)} - placeholder="请输入群昵称" - size="small" - style={{ flex: 1 }} - /> -
- ) : ( -
- - {contractInfo.selfDisplyName || "点击添加群昵称"} - -
- )} -
-
- + { + if (changedKeys.includes("groupName")) { + await handleSaveGroupName(values, ["groupName"]); + } + if (changedKeys.includes("selfDisplayName")) { + await handleSaveSelfDisplayName(values, ["selfDisplayName"]); + } + return Promise.resolve(); + }} + onSaveSuccess={(values, changedKeys) => { + // 更新本地值 + if (changedKeys.includes("groupName")) { + setGroupNameValue(values.groupName); + } + if (changedKeys.includes("selfDisplayName")) { + setSelfDisplayNameValue(values.selfDisplayName); + } + }} + isGroup={true} + /> ) : ( // 好友信息 - <> -
- - 微信号: - - {contractInfo.alias || contractInfo.wechatId} - -
-
- - 电话: - {contractInfo.phone} -
-
- - 地区: - {contractInfo.region} -
- - )} - {!isGroup && ( -
- - 备注: -
- {isEditingRemark ? ( -
- setRemarkValue(e.target.value)} - placeholder="请输入备注" - size="small" - style={{ flex: 1 }} - /> -
- ) : ( -
- {contractInfo.conRemark || "点击添加备注"} -
- )} -
-
+ { + // 更新本地值 + if (changedKeys.includes("conRemark")) { + setRemarkValue(values.conRemark); + } + }} + isGroup={false} + /> )}
@@ -790,27 +1000,25 @@ const Person: React.FC = ({ contract }) => {
{/* 渲染所有可用标签,选中的排在前面 */} - {[...new Set([...selectedTags, ...allAvailableTags])].map( - (tag, index) => { - const isSelected = selectedTags.includes(tag); - return ( - handleTagToggle(tag)} - > - {tag} - - ); - }, - )} + {[...new Set([...selectedTags, ...allAvailableTags])].map(tag => { + const isSelected = selectedTags.includes(tag); + return ( + handleTagToggle(tag)} + > + {tag} + + ); + })} {/* 新增标签区域 */} {isAddingTag ? ( @@ -933,29 +1141,7 @@ const Person: React.FC = ({ contract }) => { >