From 2e5e8414c93f8bdf1124b568fba92b8f448b9fd7 Mon Sep 17 00:00:00 2001 From: leeing <372711472@qq.com> Date: Fri, 22 Sep 2023 13:50:29 +0800 Subject: [PATCH] update uikit version --- react/package.json | 2 +- react/src/YXUIKit/im-kit-ui/package.json | 33 +- .../YXUIKit/im-kit-ui/src/chat/Container.tsx | 2 +- .../ChatMentionMemberList.tsx | 6 +- .../components/ChatMessageInput/index.tsx | 7 +- .../src/chat/containers/p2pChatContainer.tsx | 427 +++++----- .../src/chat/containers/teamChatContainer.tsx | 741 ++++++++++-------- .../components/CommonParseSession/index.tsx | 49 +- react/src/YXUIKit/im-kit-ui/src/constant.ts | 2 - .../im-kit-ui/src/conversation/Container.tsx | 14 +- react/src/YXUIKit/im-kit-ui/src/urlToBlob.ts | 16 + vue/package.json | 2 +- 12 files changed, 727 insertions(+), 574 deletions(-) create mode 100644 react/src/YXUIKit/im-kit-ui/src/urlToBlob.ts diff --git a/react/package.json b/react/package.json index f96a70d..a6e4872 100644 --- a/react/package.json +++ b/react/package.json @@ -9,7 +9,7 @@ "dependencies": { "@xkit-yx/call-kit": "^2.0.1", "@xkit-yx/call-kit-react-ui": "^0.4.1", - "@xkit-yx/im-kit-ui": "^9.6.0", + "@xkit-yx/im-kit-ui": "^9.6.2", "react-dom": "^16.8.0", "umi": "^3.5.40" }, diff --git a/react/src/YXUIKit/im-kit-ui/package.json b/react/src/YXUIKit/im-kit-ui/package.json index 3b76920..c88e8d6 100644 --- a/react/src/YXUIKit/im-kit-ui/package.json +++ b/react/src/YXUIKit/im-kit-ui/package.json @@ -1,35 +1,38 @@ { - "_from": "@xkit-yx/im-kit-ui@^9.6.0", - "_id": "@xkit-yx/im-kit-ui@9.6.0", + "_from": "@xkit-yx/im-kit-ui@^9.6.2", + "_id": "@xkit-yx/im-kit-ui@9.6.2", "_inBundle": false, - "_integrity": "sha512-J5t6zWcqjtW6PQv4t8Mj3JmY2yUaGJ8X5lxxCoivtiEl5hNXOAA3U7REcdFhxkeq7TFSl0eXxeWokmfcg7mGuA==", + "_integrity": "sha512-vFleF0DzxDgYJQnOWQKU6gkOC7Okqk8X1xvADTHVSK7toL8lHPP2PL/qEnQWOa0lffdQOYFy3B3OJxS+It1/+Q==", "_location": "/@xkit-yx/im-kit-ui", - "_phantomChildren": {}, + "_phantomChildren": { + "axios": "0.27.2", + "eventemitter3": "4.0.7" + }, "_requested": { "type": "range", "registry": true, - "raw": "@xkit-yx/im-kit-ui@^9.6.0", + "raw": "@xkit-yx/im-kit-ui@^9.6.2", "name": "@xkit-yx/im-kit-ui", "escapedName": "@xkit-yx%2fim-kit-ui", "scope": "@xkit-yx", - "rawSpec": "^9.6.0", + "rawSpec": "^9.6.2", "saveSpec": null, - "fetchSpec": "^9.6.0" + "fetchSpec": "^9.6.2" }, "_requiredBy": [ "/" ], - "_resolved": "https://registry.npmjs.org/@xkit-yx/im-kit-ui/-/im-kit-ui-9.6.0.tgz", - "_shasum": "14695835abff91d327fcbf0b4dcdeda746dd7bcf", - "_spec": "@xkit-yx/im-kit-ui@^9.6.0", + "_resolved": "https://registry.npmjs.org/@xkit-yx/im-kit-ui/-/im-kit-ui-9.6.2.tgz", + "_shasum": "e9dd04a9d359c36fa404900ec28aca70bc8ee022", + "_spec": "@xkit-yx/im-kit-ui@^9.6.2", "_where": "/Users/leeing/Documents/yunxin-projects/nim-uikit-web/react", "author": "", "bundleDependencies": false, "dependencies": { "@ant-design/icons": "^5.0.1", - "@xkit-yx/core-kit": "^0.10.6", - "@xkit-yx/im-store": "^0.0.11", - "@xkit-yx/utils": "^0.5.5", + "@xkit-yx/core-kit": "^0.10.8", + "@xkit-yx/im-store": "^0.0.13", + "@xkit-yx/utils": "^0.5.6", "antd": "^4.16.3", "mobx": "^6.6.1", "mobx-react": "^7.5.2", @@ -66,7 +69,7 @@ "es", "copySourceCode.js" ], - "gitHead": "b3bbc966437ee1b3e69ef1c4ffeae88003030588", + "gitHead": "3b90601cf3d29a01c3aa986043a84af0c04562d0", "license": "MIT", "main": "lib/index.js", "module": "es/index.js", @@ -90,5 +93,5 @@ "start": "rollup -cw --environment DEV" }, "typings": "es/index.d.ts", - "version": "9.6.0" + "version": "9.6.2" } diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/Container.tsx index f0a8ff6..f3cad44 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/Container.tsx @@ -14,7 +14,7 @@ import { observer } from 'mobx-react' import packageJson from '../../package.json' import { GroupItemProps } from './components/ChatTeamSetting/GroupItem' -import { MenuItem, MenuItemKey } from './components/ChatMessageItem' +import { MenuItem } from './components/ChatMessageItem' import { SettingActionItemProps } from './components/ChatActionBar' export interface ActionRenderProps extends ChatMessageInputProps { diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/ChatMentionMemberList.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/ChatMentionMemberList.tsx index 3a707a2..1b48ef6 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/ChatMentionMemberList.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/ChatMentionMemberList.tsx @@ -8,7 +8,7 @@ import { import { TeamMember } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' import classNames from 'classnames' import { observer } from 'mobx-react' -import { AT_ALL_ACCOUNT } from '@xkit-yx/im-store' +import { storeConstants } from '@xkit-yx/im-store' export type MentionedMember = { account: string; appellation: string } @@ -55,7 +55,7 @@ export const ChatAtMemberList: React.FC = observer( } else if (e.key === 'Enter') { if (activeIndex === -1) { onSelect?.({ - account: AT_ALL_ACCOUNT, + account: storeConstants.AT_ALL_ACCOUNT, appellation: t('teamAll'), }) } else { @@ -87,7 +87,7 @@ export const ChatAtMemberList: React.FC = observer( })} onClick={() => onSelect?.({ - account: AT_ALL_ACCOUNT, + account: storeConstants.AT_ALL_ACCOUNT, appellation: t('teamAll'), }) } diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/index.tsx index f53f2e8..73714d4 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/index.tsx @@ -19,7 +19,7 @@ import { } from '../../../common' import { Action } from '../../Container' import { MAX_UPLOAD_FILE_SIZE } from '../../../constant' -import { AT_ALL_ACCOUNT } from '@xkit-yx/im-store' +import { storeConstants } from '@xkit-yx/im-store' import { LoadingOutlined, CloseOutlined } from '@ant-design/icons' import { TMsgScene } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import { observer } from 'mobx-react' @@ -245,7 +245,10 @@ const ChatMessageInput = observer( if (selectedAtMembers.length) { selectedAtMembers .filter((member) => { - if (!allowAtAll && member.account === AT_ALL_ACCOUNT) { + if ( + !allowAtAll && + member.account === storeConstants.AT_ALL_ACCOUNT + ) { return false } return true diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/containers/p2pChatContainer.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/containers/p2pChatContainer.tsx index 87bc841..b4b26d8 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/containers/p2pChatContainer.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/containers/p2pChatContainer.tsx @@ -4,6 +4,7 @@ import React, { useState, useLayoutEffect, useMemo, + useCallback, } from 'react' import ChatActionBar from '../components/ChatActionBar' import ChatHeader from '../components/ChatHeader' @@ -31,7 +32,7 @@ import { } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import { MenuItemKey } from '../components/ChatMessageItem' import { message } from 'antd' -import { HISTORY_LIMIT } from '../../constant' +import { storeConstants } from '@xkit-yx/im-store' import { observer } from 'mobx-react' import ChatForwardModal from '../components/ChatForwardModal' @@ -129,184 +130,243 @@ const P2pChatContainer: React.FC = observer( undefined ) - const onMsgListScrollHandler = debounce(async () => { - if (messageListContainerDomRef.current) { - if ( - // 滚动到最底部了 - messageListContainerDomRef.current.scrollTop >= - messageListContainerDomRef.current.scrollHeight - - messageListContainerDomRef.current.clientHeight - - 200 - ) { - setReceiveMsgBtnVisible(false) - } else if ( - // 滚动到顶部了 - messageListContainerDomRef.current.scrollTop < 10 && - !loadingMore && - !noMore - ) { - const _msg = msgs.filter( - (item) => - !( - item.type === 'custom' && - ['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') - ) - )[0] - if (_msg) { - await getHistory(_msg.time, _msg.idServer) - // 滚动到加载的那条消息 - document.getElementById(_msg.idClient)?.scrollIntoView() + const getHistory = useCallback( + async (endTime: number, lastMsgId?: string) => { + try { + setLoadingMore(true) + const historyMsgs = await store.msgStore.getHistoryMsgActive({ + sessionId, + endTime, + lastMsgId, + limit: storeConstants.HISTORY_LIMIT, + }) + + setLoadingMore(false) + if (historyMsgs.length < storeConstants.HISTORY_LIMIT) { + setNoMore(true) } + } catch (error) { + setLoadingMore(false) + message.error(t('getHistoryMsgFailedText')) } + }, + [sessionId, store.msgStore, t] + ) + + // 收消息,发消息时需要调用 + const scrollToBottom = useCallback(() => { + if (messageListContainerDomRef.current) { + messageListContainerDomRef.current.scrollTop = + messageListContainerDomRef.current.scrollHeight } - }, 300) - - const onActionClick = (action: ChatAction) => { - const settingAction = settingActions?.find( - (item) => item.action === action - ) - if (settingAction?.onClick) { - return settingAction?.onClick() - } - switch (action) { - case 'chatSetting': - setAction(action) - setSettingDrawerVisible(true) - break - default: - break - } - } + setReceiveMsgBtnVisible(false) + }, []) + + const onMsgListScrollHandler = useCallback( + debounce(async () => { + if (messageListContainerDomRef.current) { + if ( + // 滚动到最底部了 + messageListContainerDomRef.current.scrollTop >= + messageListContainerDomRef.current.scrollHeight - + messageListContainerDomRef.current.clientHeight - + 200 + ) { + setReceiveMsgBtnVisible(false) + } else if ( + // 滚动到顶部了 + messageListContainerDomRef.current.scrollTop < 10 && + !loadingMore && + !noMore + ) { + const _msg = msgs.filter( + (item) => + !( + item.type === 'custom' && + ['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') + ) + )[0] + if (_msg) { + await getHistory(_msg.time, _msg.idServer) + // 滚动到加载的那条消息 + document.getElementById(_msg.idClient)?.scrollIntoView() + } + } + } + }, 300), + [loadingMore, msgs, noMore, getHistory] + ) - const onSettingDrawerClose = () => { + const onActionClick = useCallback( + (action: ChatAction) => { + const settingAction = settingActions?.find( + (item) => item.action === action + ) + if (settingAction?.onClick) { + return settingAction?.onClick() + } + switch (action) { + case 'chatSetting': + setAction(action) + setSettingDrawerVisible(true) + break + default: + break + } + }, + [settingActions] + ) + + const onSettingDrawerClose = useCallback(() => { setAction(undefined) setSettingDrawerVisible(false) - } + }, []) + + const onReeditClick = useCallback( + (msg: IMMessage) => { + setInputValue(msg.attach?.oldBody || '') + const replyMsg = replyMsgsMap[msg.idClient] + replyMsg && store.msgStore.replyMsgActive(replyMsg) + chatMessageInputRef.current?.input?.focus() + }, + [replyMsgsMap, store.msgStore] + ) - const onReeditClick = (msg: IMMessage) => { - setInputValue(msg.attach?.oldBody || '') - const replyMsg = replyMsgsMap[msg.idClient] - replyMsg && store.msgStore.replyMsgActive(replyMsg) - chatMessageInputRef.current?.input?.focus() - } + const onResend = useCallback( + async (msg: IMMessage) => { + try { + await store.msgStore.resendMsgActive(msg) + } catch (error) { + // message.error(t('sendMsgFailedText')) + } finally { + scrollToBottom() + } + }, + [scrollToBottom, store.msgStore] + ) - const onResend = async (msg: IMMessage) => { - try { - await store.msgStore.resendMsgActive(msg) - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { - scrollToBottom() - } - } + const onSendText = useCallback( + async (value: string) => { + try { + if (onSendTextFromProps) { + await onSendTextFromProps({ + value, + scene, + to, + }) + } else { + await store.msgStore.sendTextMsgActive({ + scene, + to, + body: value, + }) + } + } catch (error) { + // message.error(t('sendMsgFailedText')) + } finally { + scrollToBottom() + } + }, + [onSendTextFromProps, scene, store.msgStore, to, scrollToBottom] + ) - const onSendText = async (value: string) => { - try { - if (onSendTextFromProps) { - await onSendTextFromProps({ - value, + const onSendFile = useCallback( + async (file: File) => { + try { + await store.msgStore.sendFileMsgActive({ scene, to, + file, }) - } else { - await store.msgStore.sendTextMsgActive({ + } catch (error) { + // message.error(t('sendMsgFailedText')) + } finally { + scrollToBottom() + } + }, + [scene, store.msgStore, to, scrollToBottom] + ) + + const onSendImg = useCallback( + async (file: File) => { + try { + await store.msgStore.sendImageMsgActive({ scene, to, - body: value, + file, }) + } catch (error) { + // message.error(t('sendMsgFailedText')) + } finally { + scrollToBottom() } - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { - scrollToBottom() - } - } - - const onSendFile = async (file: File) => { - try { - await store.msgStore.sendFileMsgActive({ - scene, - to, - file, - }) - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { - scrollToBottom() - } - } - - const onSendImg = async (file: File) => { - try { - await store.msgStore.sendImageMsgActive({ - scene, - to, - file, - }) - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { - scrollToBottom() - } - } + }, + [scene, store.msgStore, to, scrollToBottom] + ) - const onRemoveReplyMsg = () => { + const onRemoveReplyMsg = useCallback(() => { replyMsg && store.msgStore.removeReplyMsgActive(replyMsg.sessionId) - } - - const onMessageAction = async (key: MenuItemKey, msg: IMMessage) => { - const msgOperMenuItem = msgOperMenu?.find((item) => item.key === key) - if (msgOperMenuItem?.onClick) { - return msgOperMenuItem?.onClick(msg) - } - switch (key) { - case 'delete': - await store.msgStore.deleteMsgActive([msg]) - break - case 'recall': - await store.msgStore.reCallMsgActive(msg) - break - case 'reply': - await store.msgStore.replyMsgActive(msg) - chatMessageInputRef.current?.input?.focus() - break - case 'forward': - setForwardMessage(msg) - break - default: - break - } - } + }, [replyMsg, store.msgStore]) - const onGroupCreate = async ({ - name, - avatar, - selectedAccounts, - }: { - name: string - avatar: string - selectedAccounts: string[] - }) => { - try { - await store.teamStore.createTeamActive({ - name, - avatar, - accounts: selectedAccounts, - }) - resetSettingState() - message.success(t('createTeamSuccessText')) - } catch (error: any) { - switch (error?.code) { - // 无权限 - case 802: - message.error(t('noPermission')) + const onMessageAction = useCallback( + async (key: MenuItemKey, msg: IMMessage) => { + const msgOperMenuItem = msgOperMenu?.find((item) => item.key === key) + if (msgOperMenuItem?.onClick) { + return msgOperMenuItem?.onClick(msg) + } + switch (key) { + case 'delete': + await store.msgStore.deleteMsgActive([msg]) + break + case 'recall': + await store.msgStore.reCallMsgActive(msg) + break + case 'reply': + await store.msgStore.replyMsgActive(msg) + chatMessageInputRef.current?.input?.focus() + break + case 'forward': + setForwardMessage(msg) break default: - message.error(t('createTeamFailedText')) break } - } - } + }, + [msgOperMenu, store.msgStore] + ) + + const onGroupCreate = useCallback( + async ({ + name, + avatar, + selectedAccounts, + }: { + name: string + avatar: string + selectedAccounts: string[] + }) => { + try { + await store.teamStore.createTeamActive({ + name, + avatar, + accounts: selectedAccounts, + }) + resetSettingState() + message.success(t('createTeamSuccessText')) + } catch (error: any) { + switch (error?.code) { + // 无权限 + case 802: + message.error(t('noPermission')) + break + default: + message.error(t('createTeamFailedText')) + break + } + } + }, + [store.teamStore, t] + ) const resetSettingState = () => { setAction(undefined) @@ -314,43 +374,14 @@ const P2pChatContainer: React.FC = observer( setSettingDrawerVisible(false) } - const resetState = () => { + const resetState = useCallback(() => { resetSettingState() setInputValue('') setLoadingMore(false) setNoMore(false) setReceiveMsgBtnVisible(false) setForwardMessage(undefined) - } - - // 收消息,发消息时需要调用 - const scrollToBottom = () => { - if (messageListContainerDomRef.current) { - messageListContainerDomRef.current.scrollTop = - messageListContainerDomRef.current.scrollHeight - } - setReceiveMsgBtnVisible(false) - } - - const getHistory = async (endTime: number, lastMsgId?: string) => { - try { - setLoadingMore(true) - const historyMsgs = await store.msgStore.getHistoryMsgActive({ - sessionId, - endTime, - lastMsgId, - limit: HISTORY_LIMIT, - }) - - setLoadingMore(false) - if (historyMsgs.length < HISTORY_LIMIT) { - setNoMore(true) - } - } catch (error) { - setLoadingMore(false) - message.error(t('getHistoryMsgFailedText')) - } - } + }, []) const handleForwardModalSend = () => { scrollToBottom() @@ -422,10 +453,24 @@ const P2pChatContainer: React.FC = observer( // 切换会话时需要重新初始化 useEffect(() => { resetState() - getHistory(Date.now()).then(() => { - scrollToBottom() - }) - }, [to]) + scrollToBottom() + }, [to, resetState, scrollToBottom]) + + // 切换会话时,如果内存中除了撤回消息的其他消息小于10条(差不多一屏幕),需要拉取历史消息 + useEffect(() => { + if ( + store.msgStore + .getMsg(sessionId) + .filter( + (item) => + !['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') + ).length < 10 + ) { + getHistory(Date.now()).then(() => { + scrollToBottom() + }) + } + }, [store.msgStore, sessionId, getHistory, scrollToBottom]) // 处理消息 useEffect(() => { diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/containers/teamChatContainer.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/containers/teamChatContainer.tsx index 31c09d7..0814e60 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/containers/teamChatContainer.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/containers/teamChatContainer.tsx @@ -29,8 +29,8 @@ import { } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import { MenuItemKey, AvatarMenuItem } from '../components/ChatMessageItem' import { message } from 'antd' -import { ALLOW_AT, HISTORY_LIMIT, TAllowAt } from '../../constant' -import { AT_ALL_ACCOUNT } from '@xkit-yx/im-store' +import { ALLOW_AT, TAllowAt } from '../../constant' +import { storeConstants } from '@xkit-yx/im-store' import { Team, TeamMember, @@ -260,225 +260,284 @@ const TeamChatContainer: React.FC = observer( return {defaultTitle} }, [navHistoryStack, SETTING_NAV_TITLE_MAP, action]) - const onMsgListScrollHandler = debounce(async () => { - if (messageListContainerDomRef.current) { - if ( - // 滚动到最底部了 - messageListContainerDomRef.current.scrollTop >= - messageListContainerDomRef.current.scrollHeight - - messageListContainerDomRef.current.clientHeight - - 200 - ) { - setReceiveMsgBtnVisible(false) - } else if ( - // 滚动到顶部了 - messageListContainerDomRef.current.scrollTop < 10 && - !loadingMore && - !noMore - ) { - const _msg = msgs.filter( - (item) => - !( - item.type === 'custom' && - ['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') - ) - )[0] - if (_msg) { - await getHistory(_msg.time, _msg.idServer) - // 滚动到加载的那条消息 - document.getElementById(_msg.idClient)?.scrollIntoView() + const getHistory = useCallback( + async (endTime: number, lastMsgId?: string) => { + try { + setLoadingMore(true) + const historyMsgs = await store.msgStore.getHistoryMsgActive({ + sessionId, + endTime, + lastMsgId, + limit: storeConstants.HISTORY_LIMIT, + }) + setLoadingMore(false) + if (historyMsgs.length < storeConstants.HISTORY_LIMIT) { + setNoMore(true) } + } catch (error) { + setLoadingMore(false) + message.error(t('getHistoryMsgFailedText')) } - } - }, 300) + }, + [sessionId, store.msgStore, t] + ) - const onActionClick = (action: ChatAction) => { - const settingAction = settingActions?.find( - (item) => item.action === action - ) - if (settingAction?.onClick) { - return settingAction?.onClick() - } - switch (action) { - case 'chatSetting': - setAction(action) - setSettingDrawerVisible(true) - break - default: - break + // 收消息,发消息时需要调用 + const scrollToBottom = useCallback(() => { + if (messageListContainerDomRef.current) { + messageListContainerDomRef.current.scrollTop = + messageListContainerDomRef.current.scrollHeight } - } + setReceiveMsgBtnVisible(false) + }, []) - const onSettingDrawerClose = () => { + const onMsgListScrollHandler = useCallback( + debounce(async () => { + if (messageListContainerDomRef.current) { + if ( + // 滚动到最底部了 + messageListContainerDomRef.current.scrollTop >= + messageListContainerDomRef.current.scrollHeight - + messageListContainerDomRef.current.clientHeight - + 200 + ) { + setReceiveMsgBtnVisible(false) + } else if ( + // 滚动到顶部了 + messageListContainerDomRef.current.scrollTop < 10 && + !loadingMore && + !noMore + ) { + const _msg = msgs.filter( + (item) => + !( + item.type === 'custom' && + ['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') + ) + )[0] + if (_msg) { + await getHistory(_msg.time, _msg.idServer) + // 滚动到加载的那条消息 + document.getElementById(_msg.idClient)?.scrollIntoView() + } + } + } + }, 300), + [loadingMore, msgs, noMore, getHistory] + ) + + const onActionClick = useCallback( + (action: ChatAction) => { + const settingAction = settingActions?.find( + (item) => item.action === action + ) + if (settingAction?.onClick) { + return settingAction?.onClick() + } + switch (action) { + case 'chatSetting': + setAction(action) + setSettingDrawerVisible(true) + break + default: + break + } + }, + [settingActions] + ) + + const onSettingDrawerClose = useCallback(() => { setNavHistoryStack([]) setAction(undefined) setSettingDrawerVisible(false) - } + }, []) - const onReeditClick = (msg: IMMessage) => { - const replyMsg = replyMsgsMap[msg.idClient] - replyMsg && store.msgStore.replyMsgActive(replyMsg) - // 处理 @ 消息 - const { ext } = msg - if (ext) { - try { - const extObj = JSON.parse(ext) - const yxAitMsg = extObj.yxAitMsg - if (yxAitMsg) { - const mentionedMembers: MentionedMember[] = [] - Object.keys(yxAitMsg).forEach((key) => { - if (key === AT_ALL_ACCOUNT) { - mentionedMembers.push({ - account: AT_ALL_ACCOUNT, - appellation: t('teamAll'), - }) - } else { - const member = teamMembers.find((item) => item.account === key) - member && + const onReeditClick = useCallback( + (msg: IMMessage) => { + const replyMsg = replyMsgsMap[msg.idClient] + replyMsg && store.msgStore.replyMsgActive(replyMsg) + // 处理 @ 消息 + const { ext } = msg + if (ext) { + try { + const extObj = JSON.parse(ext) + const yxAitMsg = extObj.yxAitMsg + if (yxAitMsg) { + const mentionedMembers: MentionedMember[] = [] + Object.keys(yxAitMsg).forEach((key) => { + if (key === storeConstants.AT_ALL_ACCOUNT) { mentionedMembers.push({ - account: member.account, - appellation: store.uiStore.getAppellation({ - account: member.account, - teamId: member.teamId, - ignoreAlias: true, - }), + account: storeConstants.AT_ALL_ACCOUNT, + appellation: t('teamAll'), }) - } + } else { + const member = teamMembers.find( + (item) => item.account === key + ) + member && + mentionedMembers.push({ + account: member.account, + appellation: store.uiStore.getAppellation({ + account: member.account, + teamId: member.teamId, + ignoreAlias: true, + }), + }) + } + }) + chatMessageInputRef.current?.setSelectedAtMembers( + mentionedMembers + ) + } + } catch {} + } + setInputValue(msg.attach?.oldBody || '') + chatMessageInputRef.current?.input?.focus() + }, + [replyMsgsMap, store.msgStore, teamMembers, store.uiStore, t] + ) + + const onResend = useCallback( + async (msg: IMMessage) => { + try { + await store.msgStore.resendMsgActive(msg) + } catch (error) { + // message.error(t('sendMsgFailedText')) + } finally { + scrollToBottom() + } + }, + [scrollToBottom, store.msgStore] + ) + + const onSendText = useCallback( + async (value: string, ext?: Record) => { + try { + if (onSendTextFromProps) { + await onSendTextFromProps({ + value, + scene, + to, + }) + } else { + await store.msgStore.sendTextMsgActive({ + scene, + to, + body: value, + ext, }) - chatMessageInputRef.current?.setSelectedAtMembers(mentionedMembers) } - } catch {} - } - setInputValue(msg.attach?.oldBody || '') - chatMessageInputRef.current?.input?.focus() - } - - const onResend = async (msg: IMMessage) => { - try { - await store.msgStore.resendMsgActive(msg) - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { - scrollToBottom() - } - } + } catch (error) { + // message.error(t('sendMsgFailedText')) + } finally { + scrollToBottom() + } + }, + [onSendTextFromProps, scene, store.msgStore, to, scrollToBottom] + ) - const onSendText = async (value: string, ext?: Record) => { - try { - if (onSendTextFromProps) { - await onSendTextFromProps({ - value, + const onSendFile = useCallback( + async (file: File) => { + try { + await store.msgStore.sendFileMsgActive({ scene, to, + file, }) - } else { - await store.msgStore.sendTextMsgActive({ + } catch (error) { + // message.error(t('sendMsgFailedText')) + } finally { + scrollToBottom() + } + }, + [scene, store.msgStore, to, scrollToBottom] + ) + + const onSendImg = useCallback( + async (file: File) => { + try { + await store.msgStore.sendImageMsgActive({ scene, to, - body: value, - ext, + file, }) + } catch (error) { + // message.error(t('sendMsgFailedText')) + } finally { + scrollToBottom() } - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { - scrollToBottom() - } - } - - const onSendFile = async (file: File) => { - try { - await store.msgStore.sendFileMsgActive({ - scene, - to, - file, - }) - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { - scrollToBottom() - } - } - - const onSendImg = async (file: File) => { - try { - await store.msgStore.sendImageMsgActive({ - scene, - to, - file, - }) - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { - scrollToBottom() - } - } + }, + [scene, store.msgStore, to, scrollToBottom] + ) - const onRemoveReplyMsg = () => { + const onRemoveReplyMsg = useCallback(() => { replyMsg && store.msgStore.removeReplyMsgActive(replyMsg.sessionId) - } + }, [replyMsg, store.msgStore]) - const onMessageAction = async (key: MenuItemKey, msg: IMMessage) => { - const msgOperMenuItem = msgOperMenu?.find((item) => item.key === key) - if (msgOperMenuItem?.onClick) { - return msgOperMenuItem?.onClick(msg) - } - switch (key) { - case 'delete': - await store.msgStore.deleteMsgActive([msg]) - break - case 'recall': - await store.msgStore.reCallMsgActive(msg) - break - case 'reply': - const member = mentionMembers.find( - (item) => item.account === msg.from - ) - member && - chatMessageInputRef.current?.onAtMemberSelectHandler({ - account: member.account, - appellation: store.uiStore.getAppellation({ + const onMessageAction = useCallback( + async (key: MenuItemKey, msg: IMMessage) => { + const msgOperMenuItem = msgOperMenu?.find((item) => item.key === key) + if (msgOperMenuItem?.onClick) { + return msgOperMenuItem?.onClick(msg) + } + switch (key) { + case 'delete': + await store.msgStore.deleteMsgActive([msg]) + break + case 'recall': + await store.msgStore.reCallMsgActive(msg) + break + case 'reply': + const member = mentionMembers.find( + (item) => item.account === msg.from + ) + member && + chatMessageInputRef.current?.onAtMemberSelectHandler({ account: member.account, - teamId: member.teamId, - ignoreAlias: true, - }), - }) - await store.msgStore.replyMsgActive(msg) - chatMessageInputRef.current?.input?.focus() - break - case 'forward': - setForwardMessage(msg) - break - default: - break - } - } + appellation: store.uiStore.getAppellation({ + account: member.account, + teamId: member.teamId, + ignoreAlias: true, + }), + }) + await store.msgStore.replyMsgActive(msg) + chatMessageInputRef.current?.input?.focus() + break + case 'forward': + setForwardMessage(msg) + break + default: + break + } + }, + [store.msgStore, mentionMembers, store.uiStore, msgOperMenu] + ) - const onMessageAvatarAction = async ( - key: AvatarMenuItem, - msg: IMMessage - ) => { - switch (key) { - case 'mention': - const member = mentionMembers.find( - (item) => item.account === msg.from - ) - member && - chatMessageInputRef.current?.onAtMemberSelectHandler({ - account: member.account, - appellation: store.uiStore.getAppellation({ + const onMessageAvatarAction = useCallback( + async (key: AvatarMenuItem, msg: IMMessage) => { + switch (key) { + case 'mention': + const member = mentionMembers.find( + (item) => item.account === msg.from + ) + member && + chatMessageInputRef.current?.onAtMemberSelectHandler({ account: member.account, - teamId: member.teamId, - ignoreAlias: true, - }), - }) - break - default: - break - } - } + appellation: store.uiStore.getAppellation({ + account: member.account, + teamId: member.teamId, + ignoreAlias: true, + }), + }) + break + default: + break + } + }, + [mentionMembers, store.uiStore] + ) - const onDismissTeam = async () => { + const onDismissTeam = useCallback(async () => { try { await store.teamStore.dismissTeamActive(team.teamId) message.success(t('dismissTeamSuccessText')) @@ -493,9 +552,9 @@ const TeamChatContainer: React.FC = observer( break } } - } + }, [store.teamStore, team.teamId, t]) - const onLeaveTeam = async () => { + const onLeaveTeam = useCallback(async () => { try { await store.teamStore.leaveTeamActive(team.teamId) message.success(t('leaveTeamSuccessText')) @@ -510,140 +569,155 @@ const TeamChatContainer: React.FC = observer( break } } - } + }, [store.teamStore, team.teamId, t]) - const onTransferMemberClick = () => { + const onTransferMemberClick = useCallback(() => { setGroupTransferModalVisible(true) - } + }, []) - const handleTransferTeam = () => { + const handleTransferTeam = useCallback(() => { setGroupTransferModalVisible(false) afterTransferTeam?.(team.teamId) - } + }, [afterTransferTeam, team.teamId]) - const onAddMembersClick = () => { + const onAddMembersClick = useCallback(() => { if (team.inviteMode === 'manager' && !isGroupOwner && !isGroupManager) { message.error(t('noPermission')) } else { setGroupAddMembersVisible(true) } - } + }, [team.inviteMode, isGroupOwner, isGroupManager, t]) - const onAddTeamMember = async (accounts: string[]) => { - try { - await store.teamMemberStore.addTeamMemberActive({ - teamId: team.teamId, - accounts, - }) - message.success(t('addTeamMemberSuccessText')) - resetSettingState() - } catch (error: any) { - switch (error?.code) { - // 无权限 - case 802: - message.error(t('noPermission')) - break - default: - message.error(t('addTeamMemberFailedText')) - break + const onAddTeamMember = useCallback( + async (accounts: string[]) => { + try { + await store.teamMemberStore.addTeamMemberActive({ + teamId: team.teamId, + accounts, + }) + message.success(t('addTeamMemberSuccessText')) + resetSettingState() + } catch (error: any) { + switch (error?.code) { + // 无权限 + case 802: + message.error(t('noPermission')) + break + default: + message.error(t('addTeamMemberFailedText')) + break + } } - } - } + }, + [store.teamMemberStore, team.teamId, t] + ) - const onRemoveTeamMember = async (member: TeamMember) => { - try { - await store.teamMemberStore.removeTeamMemberActive({ - teamId: team.teamId, - accounts: [member.account], - }) - message.success(t('removeTeamMemberSuccessText')) - } catch (error: any) { - switch (error?.code) { - // 无权限 - case 802: - message.error(t('noPermission')) - break - default: - message.error(t('removeTeamMemberFailedText')) - break + const onRemoveTeamMember = useCallback( + async (member: TeamMember) => { + try { + await store.teamMemberStore.removeTeamMemberActive({ + teamId: team.teamId, + accounts: [member.account], + }) + message.success(t('removeTeamMemberSuccessText')) + } catch (error: any) { + switch (error?.code) { + // 无权限 + case 802: + message.error(t('noPermission')) + break + default: + message.error(t('removeTeamMemberFailedText')) + break + } } - } - } + }, + [store.teamMemberStore, team.teamId, t] + ) - const onUpdateTeamInfo = async (params: Partial) => { - try { - await store.teamStore.updateTeamActive({ - ...params, - teamId: team.teamId, - }) - message.success(t('updateTeamSuccessText')) - } catch (error: any) { - switch (error?.code) { - // 无权限 - case 802: - message.error(t('noPermission')) - break - default: - message.error(t('updateTeamFailedText')) - break + const onUpdateTeamInfo = useCallback( + async (params: Partial) => { + try { + await store.teamStore.updateTeamActive({ + ...params, + teamId: team.teamId, + }) + message.success(t('updateTeamSuccessText')) + } catch (error: any) { + switch (error?.code) { + // 无权限 + case 802: + message.error(t('noPermission')) + break + default: + message.error(t('updateTeamFailedText')) + break + } } - } - } + }, + [store.teamStore, team.teamId, t] + ) - const onUpdateMyMemberInfo = async (params: UpdateMyMemberInfoOptions) => { - const nickTipVisible = params.nickInTeam !== void 0 - const bitConfigVisible = params.bitConfigMask !== void 0 - try { - await store.teamMemberStore.updateMyMemberInfo(params) - if (nickTipVisible) { - message.success(t('updateMyMemberNickSuccess')) - } - if (bitConfigVisible) { - message.success(t('updateBitConfigMaskSuccess')) - } - } catch (error: any) { - switch (error?.code) { - // 无权限 - case 802: - message.error(t('noPermission')) - break - default: - { - if (nickTipVisible) { - message.error(t('updateMyMemberNickFailed')) - } - if (bitConfigVisible) { - message.error(t('updateBitConfigMaskFailed')) + const onUpdateMyMemberInfo = useCallback( + async (params: UpdateMyMemberInfoOptions) => { + const nickTipVisible = params.nickInTeam !== void 0 + const bitConfigVisible = params.bitConfigMask !== void 0 + try { + await store.teamMemberStore.updateMyMemberInfo(params) + if (nickTipVisible) { + message.success(t('updateMyMemberNickSuccess')) + } + if (bitConfigVisible) { + message.success(t('updateBitConfigMaskSuccess')) + } + } catch (error: any) { + switch (error?.code) { + // 无权限 + case 802: + message.error(t('noPermission')) + break + default: + { + if (nickTipVisible) { + message.error(t('updateMyMemberNickFailed')) + } + if (bitConfigVisible) { + message.error(t('updateBitConfigMaskFailed')) + } } - } - break + break + } } - } - } + }, + [store.teamMemberStore, t] + ) - const onTeamMuteChange = async (mute: boolean) => { - try { - await store.teamStore.muteTeamActive({ - teamId: team.teamId, - mute, - }) - message.success( - mute ? t('muteAllTeamSuccessText') : t('unmuteAllTeamSuccessText') - ) - } catch (error: any) { - switch (error?.code) { - // 无权限 - case 802: - message.error(t('noPermission')) - break - default: - message.error( - mute ? t('muteAllTeamFailedText') : t('unmuteAllTeamFailedText') - ) - break + const onTeamMuteChange = useCallback( + async (mute: boolean) => { + try { + await store.teamStore.muteTeamActive({ + teamId: team.teamId, + mute, + }) + message.success( + mute ? t('muteAllTeamSuccessText') : t('unmuteAllTeamSuccessText') + ) + } catch (error: any) { + switch (error?.code) { + // 无权限 + case 802: + message.error(t('noPermission')) + break + default: + message.error( + mute ? t('muteAllTeamFailedText') : t('unmuteAllTeamFailedText') + ) + break + } } - } - } + }, + [store.teamStore, team.teamId, t] + ) const resetSettingState = () => { setNavHistoryStack([]) @@ -661,37 +735,6 @@ const TeamChatContainer: React.FC = observer( setForwardMessage(undefined) }, []) - // 收消息,发消息时需要调用 - const scrollToBottom = () => { - if (messageListContainerDomRef.current) { - messageListContainerDomRef.current.scrollTop = - messageListContainerDomRef.current.scrollHeight - } - setReceiveMsgBtnVisible(false) - } - - const getHistory = useCallback( - async (endTime: number, lastMsgId?: string) => { - try { - setLoadingMore(true) - const historyMsgs = await store.msgStore.getHistoryMsgActive({ - sessionId, - endTime, - lastMsgId, - limit: HISTORY_LIMIT, - }) - setLoadingMore(false) - if (historyMsgs.length < HISTORY_LIMIT) { - setNoMore(true) - } - } catch (error) { - setLoadingMore(false) - message.error(t('getHistoryMsgFailedText')) - } - }, - [sessionId, store.msgStore, t] - ) - const handleForwardModalSend = () => { scrollToBottom() setForwardMessage(undefined) @@ -777,12 +820,26 @@ const TeamChatContainer: React.FC = observer( // 切换会话时需要重新初始化 useEffect(() => { resetState() - getHistory(Date.now()).then(() => { - scrollToBottom() - }) + scrollToBottom() store.teamStore.getTeamActive(to) store.teamMemberStore.getTeamMemberActive(to) - }, [store.teamStore, store.teamMemberStore, to, getHistory, resetState]) + }, [store.teamStore, store.teamMemberStore, to, resetState, scrollToBottom]) + + // 切换会话时,如果内存中除了撤回消息的其他消息小于10条(差不多一屏幕),需要拉取历史消息 + useEffect(() => { + if ( + store.msgStore + .getMsg(sessionId) + .filter( + (item) => + !['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') + ).length < 10 + ) { + getHistory(Date.now()).then(() => { + scrollToBottom() + }) + } + }, [store.msgStore, sessionId, getHistory, scrollToBottom]) // 处理消息 useEffect(() => { diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/index.tsx index b6fda31..d7741ca 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/index.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useState } from 'react' +import React, { useEffect, useRef, useState } from 'react' import { Image, Popover } from 'antd' import reactStringReplace from 'react-string-replace' import CommonIcon from '../CommonIcon' @@ -11,6 +11,7 @@ import { observer } from 'mobx-react' import { parseSessionId } from '../../../utils' import { ALLOW_AT, TAllowAt } from '../../../constant' import { UserNameCard } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/UserServiceInterface' +import { getBlobImg } from '../../../urlToBlob' // 对话框中要展示的文件icon标识 const fileIconMap = { @@ -67,6 +68,31 @@ export const ParseSession: React.FC = observer( // const { type, body, idClient, sessionId, ext } = msg const [audioIconType, setAudioIconType] = useState('icon-yuyin3') const { scene, to } = parseSessionId(msg.sessionId) + const [imgUrl, setImgUrl] = useState('') + const [replyImgUrl, setReplyImgUrl] = useState('') + + useEffect(() => { + if (msg.type === 'image' && msg.attach && msg.attach.url) { + const url = `${msg.attach.url}?download=${msg.attach.name}` + getBlobImg(url).then((blobUrl) => { + setImgUrl(blobUrl) + }) + } + }, [msg.attach, msg.type]) + + useEffect(() => { + if ( + replyMsg && + replyMsg.type === 'image' && + replyMsg.attach && + replyMsg.attach.url + ) { + const url = `${replyMsg.attach.url}?download=${replyMsg.attach.name}` + getBlobImg(url).then((blobUrl) => { + setReplyImgUrl(blobUrl) + }) + } + }, [replyMsg]) let animationFlag = false @@ -134,9 +160,7 @@ export const ParseSession: React.FC = observer( handler() } - const renderImage = (msg) => { - const { attach } = msg - const url = `${attach?.url}?download=${attach?.name}` + const renderImage = (msg, isReplyMsg: boolean) => { return (
= observer( } }} > - +
) } @@ -544,7 +575,7 @@ export const ParseSession: React.FC = observer( ) : ( <>
{nick}:
- {renderMsgContent(replyMsg)} + {renderMsgContent(replyMsg, true)} )} @@ -553,13 +584,13 @@ export const ParseSession: React.FC = observer( return null } - const renderMsgContent = (msg) => { + const renderMsgContent = (msg, isReplyMsg: boolean) => { switch (msg.type) { case 'text': case 'custom': return renderCustomText(msg) case 'image': - return renderImage(msg) + return renderImage(msg, isReplyMsg) case 'file': return renderFile(msg) case 'notification': @@ -584,7 +615,7 @@ export const ParseSession: React.FC = observer( return ( <> {renderReplyMsg()} - {renderMsgContent(msg)} + {renderMsgContent(msg, false)} ) } diff --git a/react/src/YXUIKit/im-kit-ui/src/constant.ts b/react/src/YXUIKit/im-kit-ui/src/constant.ts index 9af4e70..768f769 100644 --- a/react/src/YXUIKit/im-kit-ui/src/constant.ts +++ b/react/src/YXUIKit/im-kit-ui/src/constant.ts @@ -6,8 +6,6 @@ export const MIN_VALUE = Number.MIN_VALUE export const MAX_UPLOAD_FILE_SIZE = 100 -export const HISTORY_LIMIT = 100 - export const ALLOW_AT = 'yxAllowAt' export type TAllowAt = { yxAllowAt?: 'manager' | 'all' } diff --git a/react/src/YXUIKit/im-kit-ui/src/conversation/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/conversation/Container.tsx index 86b1f12..8f1cba4 100644 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/conversation/Container.tsx @@ -106,9 +106,9 @@ export const ConversationContainer: FC = observer( }) // 处理 team 会话列表 @ 提醒 - const [sessionList, setSessionList] = useState( - [] - ) + // const [sessionList, setSessionList] = useState( + // [] + // ) const handleSessionItemClick = async ( session: NimKitCoreTypes.ISession @@ -147,9 +147,9 @@ export const ConversationContainer: FC = observer( onSessionItemMuteChange?.(session.id, mute) } - useEffect(() => { - setSessionList([...store.uiStore.sessionList]) - }, [store.uiStore.sessionList]) + // useEffect(() => { + // setSessionList([...store.uiStore.sessionList]) + // }, [store.uiStore.sessionList]) useEffect(() => { // 订阅会话列表中 p2p 的在线离线状态 @@ -163,7 +163,7 @@ export const ConversationContainer: FC = observer( return ( => { + const res = await fetch(url) + const blob = await res.blob() + return URL.createObjectURL(blob) +} + +export const getBlobImg = async (url: string): Promise => { + if (blobImgMap[url]) { + return blobImgMap[url] + } + const blobUrl = await urlToBlob(url) + blobImgMap[url] = blobUrl + return blobUrl +} diff --git a/vue/package.json b/vue/package.json index f61e17e..c7bb8af 100644 --- a/vue/package.json +++ b/vue/package.json @@ -11,7 +11,7 @@ "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" }, "dependencies": { - "@xkit-yx/im-kit-ui": "^9.5.1", + "@xkit-yx/im-kit-ui": "^9.6.2", "react": "^18.2.0", "react-dom": "^18.2.0", "vue": "^3.2.45"