diff --git a/react/package.json b/react/package.json index 916bf8b..3a1851c 100644 --- a/react/package.json +++ b/react/package.json @@ -7,9 +7,9 @@ "start": "umi dev" }, "dependencies": { - "@xkit-yx/call-kit": "^2.0.1", - "@xkit-yx/call-kit-react-ui": "^0.4.1", - "@xkit-yx/im-kit-ui": "^9.8.0", + "@xkit-yx/call-kit": "^3.0.0", + "@xkit-yx/call-kit-react-ui": "^0.6.0", + "@xkit-yx/im-kit-ui": "^10.x", "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 04a8dc9..41732ae 100644 --- a/react/src/YXUIKit/im-kit-ui/package.json +++ b/react/src/YXUIKit/im-kit-ui/package.json @@ -1,6 +1,6 @@ { "name": "@xkit-yx/im-kit-ui", - "version": "9.8.0", + "version": "10.0.1", "description": "云信即时通讯组件", "license": "MIT", "main": "lib/index.js", @@ -33,7 +33,6 @@ "gulp-typescript": "^6.0.0-alpha.1", "less": "^4.1.1", "less-plugin-npm-import": "^2.1.0", - "nim-web-sdk-ng": "0.17.0", "postcss": "^8.3.5", "postcss-nested": "^5.0.5", "react": "^16.8.0", @@ -46,8 +45,7 @@ "typescript": "^4.6.4" }, "peerDependencies": { - "@xkit-yx/core-kit": "^0.3.2", - "@xkit-yx/utils": "^0.4.3", + "@xkit-yx/im-store-v2": "^0.0.1", "antd": "^4.16.3", "mobx": "^6.6.1", "mobx-react": "^7.5.2", @@ -56,13 +54,13 @@ }, "dependencies": { "@ant-design/icons": "^5.0.1", - "@xkit-yx/core-kit": "^0.13.0", - "@xkit-yx/im-store": "^0.2.0", - "@xkit-yx/utils": "^0.5.6", + "@xkit-yx/im-store-v2": "^0.1.1", + "@xkit-yx/utils": "^0.6.0", "antd": "^4.16.3", "mobx": "^6.6.1", "mobx-react": "^7.5.2", + "nim-web-sdk-ng": "10.2.700", "react-string-replace": "^1.1.0" }, - "gitHead": "2460a56b946207f24db9a8d74c10d3776aa5f267" + "gitHead": "4e09464e7bb40f8b578b2704e7931ea1e6928417" } 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 f3cad44..638ec32 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/Container.tsx @@ -2,24 +2,26 @@ import React, { ReactNode } from 'react' import P2pChatContainer from './containers/p2pChatContainer' import TeamChatContainer from './containers/teamChatContainer' import { useStateContext, useEventTracking, Welcome, Utils } from '../common' -import { - IMMessage, - TMsgScene, -} from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import { RenderP2pCustomMessageOptions } from './components/ChatP2pMessageList' import { RenderTeamCustomMessageOptions } from './components/ChatTeamMessageList' import { ChatMessageInputProps } from './components/ChatMessageInput' -import { Session } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/SessionServiceInterface' import { observer } from 'mobx-react' import packageJson from '../../package.json' import { GroupItemProps } from './components/ChatTeamSetting/GroupItem' import { MenuItem } from './components/ChatMessageItem' import { SettingActionItemProps } from './components/ChatActionBar' +import { + V2NIMConversationType, + V2NIMConversation, +} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types' +import sdkPkg from 'nim-web-sdk-ng/package.json' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface ActionRenderProps extends ChatMessageInputProps { - scene: TMsgScene - to: string + conversationType: V2NIMConversationType + receiverId: string } export interface Action { @@ -38,7 +40,7 @@ export interface Action { } export interface MsgOperMenuItem extends MenuItem { - onClick?: (msg: IMMessage) => void + onClick?: (msg: V2NIMMessageForUI) => void } export interface ChatSettingActionItem extends SettingActionItemProps { @@ -47,9 +49,9 @@ export interface ChatSettingActionItem extends SettingActionItemProps { export interface ChatContainerProps { /** - 自定义选中的会话 sessionId。一般不用传,内部会处理好选中逻辑 + 自定义选中的会话 conversationId。一般不用传,内部会处理好选中逻辑 */ - selectedSession?: string + selectedConversation?: string /** 消息发送按钮组配置,不传使用默认的配置 */ @@ -71,8 +73,8 @@ export interface ChatContainerProps { */ onSendText?: (data: { value: string - scene: TMsgScene - to: string + conversationType: V2NIMConversationType + receiverId: string }) => Promise<void> /** 转让群主后的回调 @@ -97,16 +99,16 @@ export interface ChatContainerProps { /** 自定义渲染 header */ - renderHeader?: (session: Session) => JSX.Element + renderHeader?: (conversation: V2NIMConversation) => JSX.Element /** 自定义渲染 p2p 聊天输入框 placeholder */ - renderP2pInputPlaceHolder?: (session: Session) => string + renderP2pInputPlaceHolder?: (conversation: V2NIMConversation) => string /** 自定义渲染群组聊天输入框 placeholder */ renderTeamInputPlaceHolder?: (params: { - session: Session + conversation: V2NIMConversation mute: boolean }) => string /** @@ -118,19 +120,25 @@ export interface ChatContainerProps { /** 自定义渲染消息头像 */ - renderMessageAvatar?: (msg: IMMessage) => JSX.Element | null | undefined + renderMessageAvatar?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined /** 自定义渲染消息昵称 */ - renderMessageName?: (msg: IMMessage) => JSX.Element | null | undefined + renderMessageName?: (msg: V2NIMMessageForUI) => JSX.Element | null | undefined /** 自定义渲染消息内容,气泡样式也需要自定义 */ - renderMessageOuterContent?: (msg: IMMessage) => JSX.Element | null | undefined + renderMessageOuterContent?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined /** 自定义渲染消息内容,气泡样式不需要自定义 */ - renderMessageInnerContent?: (msg: IMMessage) => JSX.Element | null | undefined + renderMessageInnerContent?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined /** 样式前缀 @@ -144,7 +152,7 @@ export interface ChatContainerProps { export const ChatContainer: React.FC<ChatContainerProps> = observer( ({ - selectedSession, + selectedConversation, actions, p2pSettingActions, teamSettingActions, @@ -166,29 +174,35 @@ export const ChatContainer: React.FC<ChatContainerProps> = observer( prefix = 'chat', commonPrefix = 'common', }) => { - const { store, nim, initOptions } = useStateContext() + const { store, nim } = useStateContext() - const finalSelectedSession = - selectedSession || store.uiStore.selectedSession || '' + const finalSelectedConversation = + selectedConversation || store.uiStore.selectedConversation || '' - const { scene, to } = Utils.parseSessionId(finalSelectedSession) + const receiverId = nim.V2NIMConversationIdUtil.parseConversationTargetId( + finalSelectedConversation + ) + const conversationType = nim.V2NIMConversationIdUtil.parseConversationType( + finalSelectedConversation + ) useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'ChatUIKit', - imVersion: nim.version, + imVersion: sdkPkg.version, }) - return finalSelectedSession ? ( - scene === 'p2p' ? ( + return finalSelectedConversation ? ( + conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P ? ( <P2pChatContainer prefix={prefix} commonPrefix={commonPrefix} - scene={scene} - to={to} onSendText={onSendText} actions={actions} + conversationType={conversationType} + receiverId={receiverId} settingActions={p2pSettingActions} msgOperMenu={msgOperMenu} renderP2pCustomMessage={renderP2pCustomMessage} @@ -199,12 +213,13 @@ export const ChatContainer: React.FC<ChatContainerProps> = observer( renderMessageInnerContent={renderMessageInnerContent} renderMessageOuterContent={renderMessageOuterContent} /> - ) : scene === 'team' ? ( + ) : conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM ? ( <TeamChatContainer prefix={prefix} commonPrefix={commonPrefix} - scene={scene} - to={to} + conversationType={conversationType} + receiverId={receiverId} onSendText={onSendText} actions={actions} settingActions={teamSettingActions} diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAddMembers/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAddMembers/index.tsx index b2c7e50..daa8210 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAddMembers/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAddMembers/index.tsx @@ -74,9 +74,7 @@ const ChatAddMemebers: React.FC<ChatAddMemebersProps> = ({ <div style={{ height: 450 }}> <FriendSelectContainer prefix={commonPrefix} - onSelect={(selectedList) => - setSelectedAccounts(selectedList.map((item) => item.account)) - } + onSelect={(selectedList) => setSelectedAccounts(selectedList)} selectedAccounts={selectedAccounts} /> </div> diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/index.tsx index 93dd277..00f6b95 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/index.tsx @@ -122,9 +122,7 @@ const GroupCreate: React.FC<GroupCreateProps> = ({ <div style={{ height: 450 }}> <FriendSelectContainer prefix={commonPrefix} - onSelect={(accounts) => - setSelectedAccounts(accounts.map((item) => item.account)) - } + onSelect={(accounts) => setSelectedAccounts(accounts)} selectedAccounts={selectedAccounts} /> </div> diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx index 2152d1b..7f9b45c 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx @@ -7,15 +7,12 @@ import { CrudeAvatar, SelectModal, } from '../../../common' -import { - IMMessage, - TMsgScene, -} from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' -import { parseSessionId } from '../../../utils' import { SelectModalItemProps } from '../../../common/components/SelectModal' +import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface ChatForwardModalProps { - msg: IMMessage + msg?: V2NIMMessageForUI visible: boolean onSend: () => void onCancel: () => void @@ -24,11 +21,6 @@ export interface ChatForwardModalProps { commonPrefix?: string } -export interface ChatForwardModalItemProps { - scene: TMsgScene - to: string -} - const ChatForwardModal: React.FC<ChatForwardModalProps> = ({ msg, visible, @@ -38,7 +30,7 @@ const ChatForwardModal: React.FC<ChatForwardModalProps> = ({ commonPrefix = 'common', }) => { const { t } = useTranslation() - const { store } = useStateContext() + const { nim, store } = useStateContext() const [comment, setComment] = useState('') const [isSearching, setIsSearching] = useState(false) @@ -52,29 +44,44 @@ const ChatForwardModal: React.FC<ChatForwardModalProps> = ({ const datasource: SelectModalItemProps[] = useMemo(() => { if (isSearching) { const friends = store.uiStore.friends - .filter((item) => !store.relationStore.blacklist.includes(item.account)) + .filter( + (item) => !store.relationStore.blacklist.includes(item.accountId) + ) .map((item) => ({ - key: 'p2p-' + item.account, - label: store.uiStore.getAppellation({ account: item.account }), + key: nim.V2NIMConversationIdUtil.p2pConversationId(item.accountId), + label: store.uiStore.getAppellation({ account: item.accountId }), })) const teams = store.uiStore.teamList.map((item) => ({ - key: 'team-' + item.teamId, + key: nim.V2NIMConversationIdUtil.teamConversationId(item.teamId), label: item.name || item.teamId, })) return [...friends, ...teams] } - const res = [...store.sessionStore.sessions.values()] + const res = [...store.conversationStore.conversations.values()] .map((item) => { - if (item.scene === 'p2p') { + if ( + item.type === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P + ) { return { - key: item.id, - label: store.uiStore.getAppellation({ account: item.to }), + key: item.conversationId, + label: store.uiStore.getAppellation({ + account: nim.V2NIMConversationIdUtil.parseConversationTargetId( + item.conversationId + ), + }), } } - if (item.scene === 'team') { - const team = store.teamStore.teams.get(item.to) + if ( + item.type === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM + ) { + const teamId = nim.V2NIMConversationIdUtil.parseConversationTargetId( + item.conversationId + ) + const team = store.teamStore.teams.get(teamId) return { - key: item.id, + key: item.conversationId, label: team?.name || team?.teamId || '', } } @@ -84,16 +91,23 @@ const ChatForwardModal: React.FC<ChatForwardModalProps> = ({ return res }, [ + nim.V2NIMConversationIdUtil, isSearching, - store.sessionStore.sessions, + store.conversationStore.conversations, store.teamStore.teams, store.relationStore.blacklist, store.uiStore, ]) const itemAvatarRender = (data: SelectModalItemProps) => { - const { scene, to } = parseSessionId(data.key) - if (scene === 'p2p') { + const to = nim.V2NIMConversationIdUtil.parseConversationTargetId(data.key) + const conversationType = nim.V2NIMConversationIdUtil.parseConversationType( + data.key + ) + if ( + conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P + ) { return ( <ComplexAvatarContainer prefix={commonPrefix} @@ -103,7 +117,10 @@ const ChatForwardModal: React.FC<ChatForwardModalProps> = ({ /> ) } - if (scene === 'team') { + if ( + conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM + ) { const team = store.teamStore.teams.get(to) return ( <CrudeAvatar @@ -127,17 +144,9 @@ const ChatForwardModal: React.FC<ChatForwardModalProps> = ({ } const handleOk = async (data: SelectModalItemProps[]) => { - const { scene, to } = parseSessionId(data[0].key) - if (scene && to) { + if (data[0].key && msg) { try { - await store.msgStore.forwardMsgActive( - { - msg, - scene: scene as TMsgScene, - to, - }, - comment - ) + await store.msgStore.forwardMsgActive(msg, data[0].key, comment) onSend() } catch (error) { message.error(t('forwardFailedText')) diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx index 485ca57..39aa115 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx @@ -5,13 +5,13 @@ import { useStateContext, useTranslation, } from '../../../common' -import { TeamMember } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' -import { FriendProfile } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/FriendServiceInterface' import { SelectModal } from '../../../common' import { SelectModalItemProps } from '../../../common/components/SelectModal' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMConst } from 'nim-web-sdk-ng' interface GroupActionModalProps { visible: boolean - members: (TeamMember & Partial<FriendProfile>)[] // 成员列表 + members: V2NIMTeamMember[] // 成员列表 onOk: () => void // 确认操作的回调函数 onCancel: () => void commonPrefix?: string @@ -48,7 +48,7 @@ const GroupTransferModal: React.FC<GroupActionModalProps> = ({ } catch (error: any) { switch (error?.code) { // 无权限 - case 802: + case 109427: message.error(t('noPermission')) break default: @@ -66,16 +66,18 @@ const GroupTransferModal: React.FC<GroupActionModalProps> = ({ const _showMembers = members.map((item) => { return { ...item, - key: item.account, - disabled: item.type === 'owner', + key: item.accountId, + disabled: + item.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_OWNER, label: store.uiStore.getAppellation({ - account: item.account, + account: item.accountId, teamId: item.teamId, }), } }) return _showMembers - }, [[members]]) + }, [members, store.uiStore]) const itemAvatarRender = (data: SelectModalItemProps) => { return ( 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 1b48ef6..5fb9188 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 @@ -5,10 +5,10 @@ import { useStateContext, useTranslation, } from '../../../common' -import { TeamMember } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' import classNames from 'classnames' import { observer } from 'mobx-react' -import { storeConstants } from '@xkit-yx/im-store' +import { storeConstants } from '@xkit-yx/im-store-v2' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' export type MentionedMember = { account: string; appellation: string } @@ -16,7 +16,7 @@ export interface ChatMentionMemberList { allowAtAll?: boolean prefix?: string commonPrefix?: string - mentionMembers?: TeamMember[] + mentionMembers?: V2NIMTeamMember[] onSelect?: (member: MentionedMember) => void } @@ -61,9 +61,9 @@ export const ChatAtMemberList: React.FC<ChatMentionMemberList> = observer( } else { const member = mentionMembers[activeIndex] onSelect?.({ - account: member.account, + account: member.accountId, appellation: store.uiStore.getAppellation({ - account: member.account, + account: member.accountId, teamId: member.teamId, ignoreAlias: true, }), @@ -104,12 +104,12 @@ export const ChatAtMemberList: React.FC<ChatMentionMemberList> = observer( className={classNames(`${_prefix}-item`, { [`${_prefix}-item-active`]: index === activeIndex, })} - key={member.account} + key={member.accountId} onClick={() => { onSelect?.({ - account: member.account, + account: member.accountId, appellation: store.uiStore.getAppellation({ - account: member.account, + account: member.accountId, teamId: member.teamId, ignoreAlias: true, }), @@ -121,11 +121,11 @@ export const ChatAtMemberList: React.FC<ChatMentionMemberList> = observer( prefix={commonPrefix} canClick={false} size={28} - account={member.account} + account={member.accountId} /> <span className={`${_prefix}-label`}> {store.uiStore.getAppellation({ - account: member.account, + account: member.accountId, teamId: member.teamId, })} </span> 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 5ee4692..c0bd0fc 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 @@ -14,11 +14,10 @@ import { Popover, message, Button, - Spin, Dropdown, Menu, + Spin, } from 'antd' -import { IMMessage } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import { CommonIcon, getMsgContentTipByType, @@ -28,14 +27,16 @@ import { } from '../../../common' import { Action } from '../../Container' import { MAX_UPLOAD_FILE_SIZE } from '../../../constant' -import { storeConstants } from '@xkit-yx/im-store' +import { storeConstants } from '@xkit-yx/im-store-v2' import { LoadingOutlined, CloseOutlined } from '@ant-design/icons' -import { TMsgScene } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import { observer } from 'mobx-react' import { TextAreaRef } from 'antd/lib/input/TextArea' -import { TeamMember } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' import ChatAtMemberList, { MentionedMember } from './ChatMentionMemberList' import { mergeActions } from '../../../utils' +import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMConversationType } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +import { V2NIMConst } from 'nim-web-sdk-ng' const { TextArea } = Input @@ -43,16 +44,14 @@ export interface ChatMessageInputProps { prefix?: string commonPrefix?: string placeholder?: string - replyMsg?: IMMessage - mentionMembers?: TeamMember[] - scene: TMsgScene - to: string + replyMsg?: V2NIMMessageForUI + mentionMembers?: V2NIMTeamMember[] + conversationType: V2NIMConversationType + receiverId: string actions?: Action[] mute?: boolean allowAtAll?: boolean inputValue?: string - uploadImageLoading?: boolean - uploadFileLoading?: boolean setInputValue: (value: string) => void onSendText: (value: string, ext?: Record<string, unknown>) => void onSendFile: (file: File) => void @@ -78,13 +77,11 @@ const ChatMessageInput = observer( placeholder = '', mentionMembers, actions, - scene, - to, + conversationType, + receiverId, mute = false, allowAtAll = true, inputValue = '', - uploadImageLoading = false, - uploadFileLoading = false, replyMsg, setInputValue, onSendText, @@ -207,22 +204,18 @@ const ChatMessageInput = observer( render: () => { return ( <Button size="small" disabled={mute}> - {uploadFileLoading ? ( - <Spin indicator={LoadingIcon} /> - ) : ( - <Upload - beforeUpload={onBeforeUploadFileHandler} - showUploadList={false} - disabled={mute} - // action={onUploadFileHandler} - className={`${_prefix}-icon-upload`} - > - <CommonIcon - className={`${_prefix}-icon-file`} - type="icon-wenjian" - /> - </Upload> - )} + <Upload + beforeUpload={onBeforeUploadFileHandler} + showUploadList={false} + disabled={mute} + // action={onUploadFileHandler} + className={`${_prefix}-icon-upload`} + > + <CommonIcon + className={`${_prefix}-icon-file`} + type="icon-wenjian" + /> + </Upload> </Button> ) }, @@ -262,7 +255,7 @@ const ChatMessageInput = observer( const res = mentionMembers?.filter((member) => { return store.uiStore .getAppellation({ - account: member.account, + account: member.accountId, teamId: member.teamId, }) ?.includes(atMemberSearchText.replace('@', '')) @@ -276,7 +269,7 @@ const ChatMessageInput = observer( useEffect(() => { setAtMemberSearchText('') setAtVisible(false) - }, [to]) + }, [receiverId]) useEffect(() => { if (atMemberSearchText) { @@ -594,8 +587,12 @@ const ChatMessageInput = observer( const replyMsgContent = () => { if (replyMsg) { const nick = store.uiStore.getAppellation({ - account: replyMsg.from, - teamId: replyMsg.scene === 'team' ? replyMsg.to : undefined, + account: replyMsg.senderId, + teamId: + replyMsg.conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM + ? replyMsg.receiverId + : undefined, ignoreAlias: true, }) let content = `${t('replyText')} ${nick}:` diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageItem/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageItem/index.tsx index 96f20bd..46cdd90 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageItem/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageItem/index.tsx @@ -1,10 +1,6 @@ import React, { Fragment, useRef } from 'react' import { Dropdown, Menu, Tooltip } from 'antd' -import { - LoadingOutlined, - CheckCircleOutlined, - ExclamationCircleFilled, -} from '@ant-design/icons' +import { LoadingOutlined, ExclamationCircleFilled } from '@ant-design/icons' import classNames from 'classnames' import moment from 'moment' import { @@ -16,10 +12,11 @@ import { useStateContext, } from '../../../common' import { RollbackOutlined, DeleteOutlined } from '@ant-design/icons' -import { IMMessage } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import { observer } from 'mobx-react' import { MsgOperMenuItem } from '../../Container' import { mergeActions } from '../../../utils' +import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types' +import { V2NIMConst } from 'nim-web-sdk-ng' export type MenuItemKey = 'recall' | 'delete' | 'reply' | 'forward' | string export type AvatarMenuItem = 'mention' @@ -36,21 +33,24 @@ export interface MenuItem { } export interface MessageItemProps { - myAccount: string - msg: IMMessage - replyMsg?: IMMessage + msg: V2NIMMessageForUI + replyMsg?: V2NIMMessageForUI normalStatusRenderer?: React.ReactNode msgOperMenu?: MsgOperMenuItem[] - onSendImg: (file: File, randomId?: string) => Promise<void> - onSendVideo: (file: File, randomId?: string) => Promise<void> - onResend: (msg: IMMessage) => void - onReeditClick: (msg: IMMessage) => void - onMessageAction: (key: MenuItemKey, msg: IMMessage) => void - onMessageAvatarAction?: (key: AvatarMenuItem, msg: IMMessage) => void - renderMessageAvatar?: (msg: IMMessage) => JSX.Element | null | undefined - renderMessageName?: (msg: IMMessage) => JSX.Element | null | undefined - renderMessageOuterContent?: (msg: IMMessage) => JSX.Element | null | undefined - renderMessageInnerContent?: (msg: IMMessage) => JSX.Element | null | undefined + onReeditClick: (msg: V2NIMMessageForUI) => void + onResend: (msg: V2NIMMessageForUI) => void + onMessageAction: (key: MenuItemKey, msg: V2NIMMessageForUI) => void + onMessageAvatarAction?: (key: AvatarMenuItem, msg: V2NIMMessageForUI) => void + renderMessageAvatar?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined + renderMessageName?: (msg: V2NIMMessageForUI) => JSX.Element | null | undefined + renderMessageOuterContent?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined + renderMessageInnerContent?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined prefix?: string commonPrefix?: string } @@ -59,15 +59,12 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( ({ msg, replyMsg, - myAccount, normalStatusRenderer, msgOperMenu, - onResend, - onSendImg, - onSendVideo, onMessageAction, onMessageAvatarAction, onReeditClick, + onResend, renderMessageAvatar, renderMessageName, renderMessageOuterContent, @@ -81,74 +78,68 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( const _prefix = `${prefix}-message-list-item` const { - from, - // fromNick, - body, - attach, - idClient, - status, - // @ts-ignore + text, + senderId, + receiverId, + messageClientId, + sendingState, + uploadProgress, - // @ts-ignore - uploadFileInfo, - time, - type, - scene, - to, + createTime, + messageType, + conversationType, + isSelf, + recallType = '', + canRecall = false, + canEdit = false, + errorCode, } = msg const messageActionDropdownContainerRef = useRef<HTMLDivElement>(null) const messageAvatarActionDropdownContainerRef = useRef<HTMLDivElement>(null) - const isSelf = from === myAccount - const nick = store.uiStore.getAppellation({ - account: from, - teamId: scene === 'team' ? to : undefined, + account: senderId, + teamId: + conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM + ? receiverId + : undefined, }) const nickWithoutAlias = store.uiStore.getAppellation({ - account: from, - teamId: scene === 'team' ? to : undefined, + account: senderId, + teamId: + conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM + ? receiverId + : undefined, ignoreAlias: true, }) - // 内存中插入的 msg 属性,具体内容参考 msg store - const { - type: attachType = '', - canRecall = false, - canEdit = false, - oldBody = '', - } = attach || { type: '', canRecall: false, canEdit: false, oldBody: '' } - const handleResendMsg = () => { - // 如果是上传过程中失败的图片和视频消息,则重新发送 - if (uploadFileInfo && ['image', 'video'].includes(msg.type)) { - switch (msg.type) { - case 'image': - onSendImg(uploadFileInfo.file, msg.idClient) - break - case 'video': - onSendVideo(uploadFileInfo.file, msg.idClient) - break - default: - break - } - } else { - onResend(msg) - } + onResend(msg) } const renderSendStatus = () => { - if (status === 'sending') { + if ( + sendingState === + V2NIMConst.V2NIMMessageSendingState.V2NIM_MESSAGE_SENDING_STATE_SENDING + ) { return <LoadingOutlined className={`${_prefix}-status-icon`} /> } - if (status === 'read') { - return <CheckCircleOutlined className={`${_prefix}-status-icon`} /> - } - if (status === 'sendFailed') { + if ( + sendingState === + V2NIMConst.V2NIMMessageSendingState.V2NIM_MESSAGE_SENDING_STATE_FAILED + ) { + const title = + errorCode === 102426 + ? t('sendBlackFailedText') + : errorCode === 104404 + ? t('sendNotFriendFailedText') + : t('sendMsgFailedText') return ( - <Tooltip title={t('sendMsgFailedText')}> + <Tooltip title={title}> <ExclamationCircleFilled className={`${_prefix}-status-icon-fail`} onClick={handleResendMsg} @@ -156,21 +147,11 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( </Tooltip> ) } - if (status === 'refused') { - return ( - <Tooltip title={t('sendBlackFailedText')}> - <ExclamationCircleFilled - className={`${_prefix}-status-icon-fail`} - onClick={() => onResend(msg)} - /> - </Tooltip> - ) - } return normalStatusRenderer || null } const renderMsgDate = () => { - const date = moment(time) + const date = moment(createTime) const isCurrentDay = date.isSame(moment(), 'day') const isCurrentYear = date.isSame(moment(), 'year') return isCurrentDay @@ -188,7 +169,12 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( // icon: <CopyOutlined />, // }, { - show: ['sending', 'sendFailed', 'refused', 'delete'].includes(status) + show: [ + V2NIMConst.V2NIMMessageSendingState + .V2NIM_MESSAGE_SENDING_STATE_SENDING, + V2NIMConst.V2NIMMessageSendingState + .V2NIM_MESSAGE_SENDING_STATE_FAILED, + ].includes(sendingState) ? 0 : 1, label: t('replyText'), @@ -203,8 +189,13 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( }, { show: - ['sending', 'sendFailed', 'refused', 'delete'].includes(status) || - type === 'audio' + [ + V2NIMConst.V2NIMMessageSendingState + .V2NIM_MESSAGE_SENDING_STATE_SENDING, + V2NIMConst.V2NIMMessageSendingState + .V2NIM_MESSAGE_SENDING_STATE_FAILED, + ].includes(sendingState) || + messageType === V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO ? 0 : 1, label: t('forwardText'), @@ -236,8 +227,8 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( const renderSpecialMsg = () => { return ( - <div key={idClient} className={`${_prefix}-recall`}> - {attachType === 'reCallMsg' ? ( + <div key={messageClientId} className={`${_prefix}-recall`}> + {recallType === 'reCallMsg' ? ( <> {`${t('you')}${t('recallMessageText')}`} {canEdit ? ( @@ -256,9 +247,10 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( ) } - return attachType === 'reCallMsg' || attachType === 'beReCallMsg' ? ( + return recallType === 'reCallMsg' || recallType === 'beReCallMsg' ? ( renderSpecialMsg() - ) : type === 'notification' ? ( + ) : messageType === + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION ? ( <ParseSession replyMsg={replyMsg} msg={msg} prefix={commonPrefix} /> ) : ( <div @@ -272,7 +264,7 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( <MyAvatarContainer prefix={commonPrefix} canClick={false} /> ) : ( <Dropdown - key={idClient} + key={messageClientId} trigger={['contextMenu']} overlay={ onMessageAvatarAction ? ( @@ -296,7 +288,7 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( > <ComplexAvatarContainer prefix={commonPrefix} - account={from} + account={senderId} /> </div> </Dropdown> @@ -305,7 +297,7 @@ export const ChatMessageItem: React.FC<MessageItemProps> = observer( )} <Dropdown - key={idClient} + key={messageClientId} trigger={['contextMenu']} overlay={ <Menu diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatP2pMessageList/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatP2pMessageList/index.tsx index c0fd99a..bf89291 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatP2pMessageList/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatP2pMessageList/index.tsx @@ -1,24 +1,22 @@ import React, { forwardRef } from 'react' -import { IMMessage } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import MessageListItem, { MessageItemProps } from '../ChatMessageItem' import { Alert, Spin } from 'antd' import { ArrowDownOutlined } from '@ant-design/icons' import { useTranslation, ReadPercent, useStateContext } from '../../../common' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' -import { storeUtils } from '@xkit-yx/im-store' +import { storeUtils } from '@xkit-yx/im-store-v2' import { MsgOperMenuItem } from '../../Container' import { observer } from 'mobx-react' +import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types' -export interface RenderP2pCustomMessageOptions - extends Omit<MessageItemProps, 'myAccount'> { - member: NimKitCoreTypes.IFriendInfo +export interface RenderP2pCustomMessageOptions extends MessageItemProps { + receiverId: string } export interface ChatP2pMessageListProps extends Omit<MessageItemProps, 'msg' | 'alias'> { - msgs: IMMessage[] - replyMsgsMap: Record<string, IMMessage> - member: NimKitCoreTypes.IFriendInfo + msgs: V2NIMMessageForUI[] + replyMsgsMap: Record<string, V2NIMMessageForUI> + receiverId: string renderP2pCustomMessage?: ( options: RenderP2pCustomMessageOptions ) => JSX.Element | null | undefined @@ -39,17 +37,14 @@ const ChatP2pMessageList = observer( commonPrefix = 'common', msgs, replyMsgsMap, - member, + receiverId, receiveMsgBtnVisible = false, msgReceiptTime = 0, msgOperMenu, onReceiveMsgBtnClick, loadingMore, noMore, - myAccount, onResend, - onSendImg, - onSendVideo, onMessageAction, onReeditClick, onScroll, @@ -78,34 +73,29 @@ const ChatP2pMessageList = observer( {renderMsgs.map((msg) => { const msgItem = renderP2pCustomMessage?.({ msg, - replyMsg: replyMsgsMap[msg.idClient], - member, + replyMsg: replyMsgsMap[msg.messageClientId], + receiverId, onResend, - onSendImg, - onSendVideo, onReeditClick, onMessageAction, }) ?? ( <MessageListItem - key={msg.idClient} + key={msg.messageClientId} prefix={prefix} commonPrefix={commonPrefix} msg={msg} msgOperMenu={msgOperMenu} - replyMsg={replyMsgsMap[msg.idClient]} + replyMsg={replyMsgsMap[msg.messageClientId]} normalStatusRenderer={ localOptions.p2pMsgReceiptVisible ? ( <ReadPercent - unread={msg.time <= msgReceiptTime ? 0 : 1} - read={msg.time <= msgReceiptTime ? 1 : 0} + unread={msg.createTime <= msgReceiptTime ? 0 : 1} + read={msg.createTime <= msgReceiptTime ? 1 : 0} prefix={commonPrefix} /> ) : null } - myAccount={myAccount} onResend={onResend} - onSendImg={onSendImg} - onSendVideo={onSendVideo} onMessageAction={onMessageAction} onReeditClick={onReeditClick} renderMessageAvatar={renderMessageAvatar} @@ -115,7 +105,7 @@ const ChatP2pMessageList = observer( /> ) return ( - <div id={msg.idClient} key={msg.idClient}> + <div id={msg.messageClientId} key={msg.messageClientId}> {msgItem} </div> ) @@ -130,13 +120,13 @@ const ChatP2pMessageList = observer( <ArrowDownOutlined /> </div> ) : null} - {store.uiStore.getRelation(member.account).relation === 'stranger' ? ( + {store.uiStore.getRelation(receiverId).relation === 'stranger' ? ( <Alert className={`${_prefix}-stranger-noti`} banner closable message={`${store.uiStore.getAppellation({ - account: member.account, + account: receiverId, })} ${t('strangerNotiText')}`} /> ) : null} diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMemberModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMemberModal/index.tsx index f1f5d75..e031974 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMemberModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMemberModal/index.tsx @@ -8,6 +8,7 @@ import { } from '../../../common' import { observer } from 'mobx-react' import { SelectModalItemProps } from '../../../common/components/SelectModal' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface ChatTeamMemberModalProps { visible: boolean @@ -31,19 +32,23 @@ const ChatTeamMemberModal: React.FC<ChatTeamMemberModalProps> = observer( const teamMembers = store.teamMemberStore .getTeamMember(teamId) - .filter((item) => item.account !== store.userStore.myUserInfo.account) + .filter((item) => item.accountId !== store.userStore.myUserInfo.accountId) const datasource = teamMembers.map((item) => ({ - key: item.account, + key: item.accountId, label: store.uiStore.getAppellation({ - account: item.account, + account: item.accountId, teamId: item.teamId, }), })) const teamManagerAccounts = teamMembers - .filter((item) => item.type === 'manager') - .map((item) => item.account) + .filter( + (item) => + item.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_MANAGER + ) + .map((item) => item.accountId) const itemAvatarRender = (data: SelectModalItemProps) => { return ( @@ -65,25 +70,27 @@ const ChatTeamMemberModal: React.FC<ChatTeamMemberModalProps> = observer( data.every((j) => j.key !== i) ) add.length && - (await store.teamStore.addTeamManagersActive({ + (await store.teamStore.updateTeamMemberRoleActive({ teamId, accounts: add, + role: V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_MANAGER, })) remove.length && - (await store.teamStore.removeTeamManagersActive({ + (await store.teamStore.updateTeamMemberRoleActive({ teamId, accounts: remove, + role: V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_NORMAL, })) message.success(t('updateTeamManagerSuccessText')) onCancel() } catch (error: any) { switch (error?.code) { // 操作的人不在群中 - case 804: + case 191004: message.error(t('userNotInTeam')) break // 没权限 - case 802: + case 109432: message.error(t('noPermission')) break default: diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMessageList/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMessageList/index.tsx index 73dc824..bd75074 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMessageList/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMessageList/index.tsx @@ -1,24 +1,23 @@ import React, { forwardRef } from 'react' -import { IMMessage } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' -import { TeamMember } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' import MessageListItem, { MessageItemProps } from '../ChatMessageItem' import { Alert, Spin } from 'antd' import { ArrowDownOutlined } from '@ant-design/icons' import { ReadPercent, useStateContext, useTranslation } from '../../../common' -import { storeUtils } from '@xkit-yx/im-store' +import { storeUtils } from '@xkit-yx/im-store-v2' import { MsgOperMenuItem } from '../../Container' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types' -export interface RenderTeamCustomMessageOptions - extends Omit<MessageItemProps, 'myAccount'> { - members: TeamMember[] +export interface RenderTeamCustomMessageOptions extends MessageItemProps { + members: V2NIMTeamMember[] } export interface ChatTeamMessageListProps extends Omit<MessageItemProps, 'msg' | 'alias'> { - msgs: IMMessage[] + msgs: V2NIMMessageForUI[] msgOperMenu?: MsgOperMenuItem[] - replyMsgsMap: Record<string, IMMessage> - members: TeamMember[] + replyMsgsMap: Record<string, V2NIMMessageForUI> + members: V2NIMTeamMember[] renderTeamCustomMessage?: ( options: RenderTeamCustomMessageOptions ) => JSX.Element | null | undefined @@ -49,10 +48,7 @@ const ChatTeamMessageList = forwardRef< onReceiveMsgBtnClick, loadingMore, noMore, - myAccount, onResend, - onSendImg, - onSendVideo, onMessageAction, onMessageAvatarAction, onReeditClick, @@ -82,35 +78,30 @@ const ChatTeamMessageList = forwardRef< {renderMsgs.map((msg) => { const msgItem = renderTeamCustomMessage?.({ msg, - replyMsg: replyMsgsMap[msg.idClient], + replyMsg: replyMsgsMap[msg.messageClientId], members, onResend, - onSendImg, - onSendVideo, onReeditClick, onMessageAction, }) ?? ( <MessageListItem - key={msg.idClient} + key={msg.messageClientId} prefix={prefix} commonPrefix={commonPrefix} msg={msg} msgOperMenu={msgOperMenu} - replyMsg={replyMsgsMap[msg.idClient]} + replyMsg={replyMsgsMap[msg.messageClientId]} normalStatusRenderer={ localOptions.teamMsgReceiptVisible ? ( <ReadPercent - unread={msg.attach?.yxUnread ?? members.length - 1} - read={msg.attach?.yxRead ?? 0} + unread={msg.yxUnread ?? members.length - 1} + read={msg.yxRead ?? 0} hoverable prefix={commonPrefix} /> ) : null } - myAccount={myAccount} onResend={onResend} - onSendImg={onSendImg} - onSendVideo={onSendVideo} onMessageAction={onMessageAction} onMessageAvatarAction={onMessageAvatarAction} onReeditClick={onReeditClick} @@ -121,7 +112,7 @@ const ChatTeamMessageList = forwardRef< /> ) return ( - <div id={msg.idClient} key={msg.idClient}> + <div id={msg.messageClientId} key={msg.messageClientId}> {msgItem} </div> ) diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupDetail.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupDetail.tsx index 4201167..2091c71 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupDetail.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupDetail.tsx @@ -1,12 +1,15 @@ import React, { FC, useEffect, useState } from 'react' import { Form, Input, Button } from 'antd' import { GroupAvatarSelect, useTranslation, CrudeAvatar } from '../../../common' -import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { + V2NIMTeam, + V2NIMUpdatedTeamInfo, +} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' export interface GroupDetailmProps { - team: Team + team: V2NIMTeam hasPower: boolean - onUpdateTeamInfo: (team: Partial<Team>) => void + onUpdateTeamInfo: (team: V2NIMUpdatedTeamInfo) => void prefix?: string commonPrefix?: string @@ -43,13 +46,13 @@ const GroupDetail: FC<GroupDetailmProps> = ({ }, [team.name]) const onUpdateTeamInfoSubmitHandler = () => { - const obj: Partial<Team> = { avatar, name, intro } + const obj: V2NIMUpdatedTeamInfo = { avatar, name, intro } Object.keys(obj).forEach((key) => { if (obj[key] === team[key]) { delete obj[key] } }) - onUpdateTeamInfo({ ...obj, teamId: team.teamId }) + onUpdateTeamInfo(obj) } return ( diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupItem.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupItem.tsx index 380d50b..618583c 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupItem.tsx @@ -6,13 +6,14 @@ import { useTranslation, useStateContext, } from '../../../common' -import { TeamMember } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' import { observer } from 'mobx-react' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface GroupItemProps { - myMemberInfo: TeamMember - member: TeamMember - onRemoveTeamMemberClick: (member: TeamMember) => void + myMemberInfo: V2NIMTeamMember + member: V2NIMTeamMember + onRemoveTeamMemberClick: (member: V2NIMTeamMember) => void afterSendMsgClick?: () => void prefix?: string @@ -39,7 +40,7 @@ export const GroupItem: FC<GroupItemProps> = observer( const [isActive, setIsActive] = useState(false) - const isSelf = member.account === myMemberInfo.account + const isSelf = member.accountId === myMemberInfo.accountId const renderRemoveBtn = () => { return ( @@ -64,21 +65,34 @@ export const GroupItem: FC<GroupItemProps> = observer( } const renderButton = () => { - if (member.type === 'owner') { + if ( + member.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_OWNER + ) { return <span className={`${_prefix}-owner`}>{t('teamOwnerText')}</span> } - if (member.type === 'manager') { - return myMemberInfo.type === 'owner' && isActive ? ( + if ( + member.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_MANAGER + ) { + return myMemberInfo.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_OWNER && + isActive ? ( renderRemoveBtn() ) : ( <span className={`${_prefix}-manager`}>{t('teamManagerText')}</span> ) } - if (member.type === 'normal') { - return (myMemberInfo.type === 'owner' || - myMemberInfo.type === 'manager') && + if ( + member.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_NORMAL + ) { + return (myMemberInfo.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_OWNER || + myMemberInfo.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_MANAGER) && isActive && !isSelf ? renderRemoveBtn() @@ -104,11 +118,11 @@ export const GroupItem: FC<GroupItemProps> = observer( prefix={commonPrefix} afterSendMsgClick={afterSendMsgClick} canClick={!isSelf} - account={member.account} + account={member.accountId} /> <span className={`${_prefix}-label`}> {store.uiStore.getAppellation({ - account: member.account, + account: member.accountId, teamId: member.teamId, })} </span> diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupList.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupList.tsx index e385c51..9c6891b 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupList.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupList.tsx @@ -1,14 +1,14 @@ import React, { FC, useMemo, useState } from 'react' import { GroupItem, GroupItemProps } from './GroupItem' -import { TeamMember } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' import { Input } from 'antd' import { SearchOutlined } from '@ant-design/icons' import { useStateContext, useTranslation } from '../../../common' export interface GroupListProps { - myMemberInfo: TeamMember - members: TeamMember[] - onRemoveTeamMemberClick: (member: TeamMember) => void + myMemberInfo: V2NIMTeamMember + members: V2NIMTeamMember[] + onRemoveTeamMemberClick: (member: V2NIMTeamMember) => void afterSendMsgClick?: () => void renderTeamMemberItem?: ( params: GroupItemProps @@ -39,12 +39,12 @@ const GroupList: FC<GroupListProps> = ({ if (groupSearchText) { _sortedMembers = members.filter((item) => store.uiStore - .getAppellation({ account: item.account, teamId: item.teamId }) + .getAppellation({ account: item.accountId, teamId: item.teamId }) .includes(groupSearchText) ) } return _sortedMembers - }, [members, groupSearchText]) + }, [members, groupSearchText, store.uiStore]) return ( <div className={`${_prefix}-wrap`}> @@ -68,7 +68,7 @@ const GroupList: FC<GroupListProps> = ({ } return ( renderTeamMemberItem?.(itemProps) ?? ( - <GroupItem key={item.account} {...itemProps} /> + <GroupItem key={item.accountId} {...itemProps} /> ) ) }) diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupPower.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupPower.tsx index 6d97c19..f119396 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupPower.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupPower.tsx @@ -6,17 +6,19 @@ import { useTranslation, } from '../../../common' import { - Team, - TeamMember, -} from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' + V2NIMTeam, + V2NIMTeamMember, + V2NIMUpdatedTeamInfo, +} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' import ChatTeamMemberModal from '../ChatTeamMemberModal' import { ALLOW_AT, TAllowAt } from '../../../constant' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface GroupPowerProps { - onUpdateTeamInfo: (team: Partial<Team>) => void + onUpdateTeamInfo: (team: V2NIMUpdatedTeamInfo) => void onTeamMuteChange: (mute: boolean) => void - team: Team - managers: TeamMember[] + team: V2NIMTeam + managers: V2NIMTeamMember[] isGroupOwner: boolean afterSendMsgClick?: () => void @@ -59,12 +61,12 @@ const GroupPower: React.FC<GroupPowerProps> = ({ const ext: TAllowAt = useMemo(() => { let res = {} try { - res = JSON.parse(team.ext || '{}') + res = JSON.parse(team.serverExtension || '{}') } catch (error) { // } return res - }, [team.ext]) + }, [team.serverExtension]) return ( <div className={`${_prefix}-wrap`}> @@ -94,8 +96,8 @@ const GroupPower: React.FC<GroupPowerProps> = ({ ) : ( managers.map((item) => ( <ComplexAvatarContainer - key={item.account} - account={item.account} + key={item.accountId} + account={item.accountId} afterSendMsgClick={afterSendMsgClick} prefix={commonPrefix} /> @@ -110,9 +112,22 @@ const GroupPower: React.FC<GroupPowerProps> = ({ <Select className={`${_prefix}-who-select`} options={options} - value={team.updateTeamMode} + value={ + team.updateInfoMode === + V2NIMConst.V2NIMTeamUpdateInfoMode + .V2NIM_TEAM_UPDATE_INFO_MODE_MANAGER + ? 'manager' + : 'all' + } onChange={(value) => { - onUpdateTeamInfo({ updateTeamMode: value }) + onUpdateTeamInfo({ + updateInfoMode: + value === 'manager' + ? V2NIMConst.V2NIMTeamUpdateInfoMode + .V2NIM_TEAM_UPDATE_INFO_MODE_MANAGER + : V2NIMConst.V2NIMTeamUpdateInfoMode + .V2NIM_TEAM_UPDATE_INFO_MODE_ALL, + }) }} ></Select> </div> @@ -121,9 +136,20 @@ const GroupPower: React.FC<GroupPowerProps> = ({ <Select className={`${_prefix}-who-select`} options={options} - value={team.inviteMode} + value={ + team.inviteMode === + V2NIMConst.V2NIMTeamInviteMode.V2NIM_TEAM_INVITE_MODE_MANAGER + ? 'manager' + : 'all' + } onChange={(value) => { - onUpdateTeamInfo({ inviteMode: value }) + onUpdateTeamInfo({ + inviteMode: + value === 'manager' + ? V2NIMConst.V2NIMTeamInviteMode + .V2NIM_TEAM_INVITE_MODE_MANAGER + : V2NIMConst.V2NIMTeamInviteMode.V2NIM_TEAM_INVITE_MODE_ALL, + }) }} ></Select> </div> @@ -135,7 +161,7 @@ const GroupPower: React.FC<GroupPowerProps> = ({ value={ext[ALLOW_AT] || 'all'} onChange={(value) => { onUpdateTeamInfo({ - ext: JSON.stringify({ ...ext, [ALLOW_AT]: value }), + serverExtension: JSON.stringify({ ...ext, [ALLOW_AT]: value }), }) }} ></Select> @@ -144,7 +170,14 @@ const GroupPower: React.FC<GroupPowerProps> = ({ <div className={`${_prefix}-action`}> <div className={`${_prefix}-action-item`}> <label>{t('teamMuteText')}</label> - <Switch checked={team.mute} onChange={onTeamMuteChange} /> + <Switch + checked={ + team.chatBannedMode !== + V2NIMConst.V2NIMTeamChatBannedMode + .V2NIM_TEAM_CHAT_BANNED_MODE_UNBAN + } + onChange={onTeamMuteChange} + /> </div> </div> <ChatTeamMemberModal diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/index.tsx index a807db9..c4ef33d 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/index.tsx @@ -12,12 +12,14 @@ import GroupDetail from './GroupDetail' import GroupList from './GroupList' import GroupPower from './GroupPower' import { GroupSettingType } from '../../types' -import { - Team, - TeamMember, -} from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' -import { UpdateMyMemberInfoOptions } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' import { GroupItemProps } from './GroupItem' +import { + V2NIMTeam, + V2NIMTeamMember, + V2NIMUpdatedTeamInfo, + V2NIMUpdateSelfMemberInfoParams, +} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface HistoryStack { path: GroupSettingType @@ -25,8 +27,8 @@ export interface HistoryStack { } export interface ChatTeamSettingProps { - members: TeamMember[] - team: Team + members: V2NIMTeamMember[] + team: V2NIMTeam myAccount: string isGroupOwner: boolean isGroupManager: boolean @@ -36,9 +38,9 @@ export interface ChatTeamSettingProps { onLeaveTeam: () => void onAddMembersClick: () => void onTransferTeamClick: () => void - onRemoveTeamMemberClick: (member: TeamMember) => void - onUpdateTeamInfo: (team: Partial<Team>) => void - onUpdateMyMemberInfo: (params: UpdateMyMemberInfoOptions) => void + onRemoveTeamMemberClick: (member: V2NIMTeamMember) => void + onUpdateTeamInfo: (team: V2NIMUpdatedTeamInfo) => void + onUpdateMyMemberInfo: (params: V2NIMUpdateSelfMemberInfoParams) => void onTeamMuteChange: (mute: boolean) => void afterSendMsgClick?: () => void setNavHistoryStack: (stack: HistoryStack[]) => void @@ -107,8 +109,7 @@ const ChatTeamSetting: FC<ChatTeamSettingProps> = ({ const handleUpdateMyMemberInfo = (e: React.FocusEvent<HTMLInputElement>) => { onUpdateMyMemberInfo({ - teamId: team.teamId, - nickInTeam, + teamNick: nickInTeam, }) } @@ -141,25 +142,38 @@ const ChatTeamSetting: FC<ChatTeamSettingProps> = ({ const isOwnerOrManager = isGroupOwner || isGroupManager const hasUpdateTeamPower = useMemo(() => { - if (team.updateTeamMode === 'manager' && isOwnerOrManager) { + if ( + team.updateInfoMode === + V2NIMConst.V2NIMTeamUpdateInfoMode + .V2NIM_TEAM_UPDATE_INFO_MODE_MANAGER && + isOwnerOrManager + ) { return true } - return team.updateTeamMode === 'all' - }, [team.updateTeamMode, isOwnerOrManager]) + return ( + team.updateInfoMode === + V2NIMConst.V2NIMTeamUpdateInfoMode.V2NIM_TEAM_UPDATE_INFO_MODE_ALL + ) + }, [team.updateInfoMode, isOwnerOrManager]) const myMemberInfo = useMemo(() => { return ( - members.find((item) => item.account === myAccount) || ({} as TeamMember) + members.find((item) => item.accountId === myAccount) || + ({} as V2NIMTeamMember) ) }, [myAccount, members]) const teamManagers = useMemo(() => { - return members.filter((item) => item.type === 'manager') + return members.filter( + (item) => + item.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_MANAGER + ) }, [members]) useEffect(() => { - setNickInTeam(myMemberInfo.nickInTeam || '') - }, [myMemberInfo.nickInTeam]) + setNickInTeam(myMemberInfo.teamNick || '') + }, [myMemberInfo.teamNick]) useEffect(() => { if (!navHistoryStack.length) { @@ -210,9 +224,9 @@ const ChatTeamSetting: FC<ChatTeamSettingProps> = ({ {members.slice(0, 6).map((item) => { return ( <ComplexAvatarContainer - key={item.account} + key={item.accountId} prefix={commonPrefix} - account={item.account} + account={item.accountId} canClick={false} /> ) @@ -231,7 +245,8 @@ const ChatTeamSetting: FC<ChatTeamSettingProps> = ({ placeholder={t('editNickInTeamText')} /> </div> - {team.type !== 'normal' && isOwnerOrManager ? ( + {team.teamType !== V2NIMConst.V2NIMTeamType.V2NIM_TEAM_TYPE_INVALID && + isOwnerOrManager ? ( <div className={`${_prefix}-power ${_prefix}-item`} onClick={handleStackPush.bind(null, 'power')} 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 3550a15..17cfefe 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 @@ -24,40 +24,47 @@ import { } from '../../common' import { Action, ChatSettingActionItem, MsgOperMenuItem } from '../Container' import ChatP2pSetting from '../components/ChatP2pSetting' -import { Session } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/SessionServiceInterface' import { debounce, VisibilityObserver } from '@xkit-yx/utils' -import { - IMMessage, - TMsgScene, -} from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import { MenuItemKey } from '../components/ChatMessageItem' import { message } from 'antd' -import { storeConstants } from '@xkit-yx/im-store' +import { storeConstants } from '@xkit-yx/im-store-v2' import { observer } from 'mobx-react' import ChatForwardModal from '../components/ChatForwardModal' import { getImgDataUrl, getVideoFirstFrameDataUrl } from '../../utils' -import { addTask, removeTask } from '../../uploadingTask' +import { + V2NIMConversationType, + V2NIMConversation, +} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +import { V2NIMMessage } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMMessageService' +import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface P2pChatContainerProps { - scene: TMsgScene - to: string + conversationType: V2NIMConversationType + receiverId: string settingActions?: ChatSettingActionItem[] actions?: Action[] msgOperMenu?: MsgOperMenuItem[] onSendText?: (data: { value: string - scene: TMsgScene - to: string + conversationType: V2NIMConversationType + receiverId: string }) => Promise<void> renderP2pCustomMessage?: ( options: RenderP2pCustomMessageOptions ) => JSX.Element | null | undefined - renderHeader?: (session: Session) => JSX.Element - renderP2pInputPlaceHolder?: (session: Session) => string - renderMessageAvatar?: (msg: IMMessage) => JSX.Element | null | undefined - renderMessageName?: (msg: IMMessage) => JSX.Element | null | undefined - renderMessageOuterContent?: (msg: IMMessage) => JSX.Element | null | undefined - renderMessageInnerContent?: (msg: IMMessage) => JSX.Element | null | undefined + renderHeader?: (conversation: V2NIMConversation) => JSX.Element + renderP2pInputPlaceHolder?: (conversation: V2NIMConversation) => string + renderMessageAvatar?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined + renderMessageName?: (msg: V2NIMMessageForUI) => JSX.Element | null | undefined + renderMessageOuterContent?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined + renderMessageInnerContent?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined prefix?: string commonPrefix?: string @@ -65,8 +72,8 @@ export interface P2pChatContainerProps { const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( ({ - scene, - to, + conversationType, + receiverId, settingActions, actions, msgOperMenu, @@ -86,26 +93,30 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( const { t } = useTranslation() - const sessionId = `${scene}-${to}` + const conversationId = + nim.V2NIMConversationIdUtil.p2pConversationId(receiverId) - const session = store.sessionStore.sessions.get(sessionId) + const conversation = + store.conversationStore.conversations.get(conversationId) - const msgs = store.msgStore.getMsg(sessionId) + const msgs = store.msgStore.getMsg(conversationId) // 当前输入框的回复消息 - const replyMsg = store.msgStore.replyMsgs.get(sessionId) + const replyMsg = store.msgStore.replyMsgs.get(conversationId) - const user = store.uiStore.getFriendWithUserNameCard(to) + const user = store.uiStore.getFriendWithUserNameCard(receiverId) const myUser = store.userStore.myUserInfo const userNickOrAccount = store.uiStore.getAppellation({ - account: user.account, + account: user.accountId, }) - const isOnline = store.eventStore.stateMap.get(to) === 'online' + // TODO sdk 暂不支持用户在线状态 + // const isOnline = store.eventStore.stateMap.get(receiverId) === 'online' + const isOnline = 'online' - const createDefaultAccounts = useMemo(() => [to], [to]) + const createDefaultAccounts = useMemo(() => [receiverId], [receiverId]) const messageListContainerDomRef = useRef<HTMLDivElement>(null) const settingDrawDomRef = useRef<HTMLDivElement>(null) @@ -115,12 +126,12 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( return new VisibilityObserver({ root: messageListContainerDomRef.current, }) - }, [to]) + }, [receiverId]) // 以下是 UI 相关的 state,需要在切换会话时重置 - const [replyMsgsMap, setReplyMsgsMap] = useState<Record<string, IMMessage>>( - {} - ) // 回复消息的 map + const [replyMsgsMap, setReplyMsgsMap] = useState< + Record<string, V2NIMMessageForUI> + >({}) // 回复消息的 map const [action, setAction] = useState<ChatAction | undefined>(undefined) const [inputValue, setInputValue] = useState('') const [groupCreateVisible, setGroupCreateVisible] = useState(false) @@ -128,16 +139,16 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( const [noMore, setNoMore] = useState(false) const [receiveMsgBtnVisible, setReceiveMsgBtnVisible] = useState(false) const [settingDrawerVisible, setSettingDrawerVisible] = useState(false) - const [forwardMessage, setForwardMessage] = useState<IMMessage | undefined>( - undefined - ) + const [forwardMessage, setForwardMessage] = useState< + V2NIMMessageForUI | undefined + >(undefined) const getHistory = useCallback( async (endTime: number, lastMsgId?: string) => { try { setLoadingMore(true) const historyMsgs = await store.msgStore.getHistoryMsgActive({ - sessionId, + conversationId, endTime, lastMsgId, limit: storeConstants.HISTORY_LIMIT, @@ -153,17 +164,20 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( message.error(t('getHistoryMsgFailedText')) } }, - [sessionId, store.msgStore, t] + [conversationId, store.msgStore, t] ) // 收消息,发消息时需要调用 - const scrollToBottom = useCallback(() => { - if (messageListContainerDomRef.current) { - messageListContainerDomRef.current.scrollTop = - messageListContainerDomRef.current.scrollHeight - } - setReceiveMsgBtnVisible(false) - }, []) + const scrollToBottom = useCallback( + debounce(() => { + if (messageListContainerDomRef.current) { + messageListContainerDomRef.current.scrollTop = + messageListContainerDomRef.current.scrollHeight + } + setReceiveMsgBtnVisible(false) + }, 300), + [] + ) const onMsgListScrollHandler = useCallback( debounce(async () => { @@ -185,14 +199,15 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( const _msg = msgs.filter( (item) => !( - item.type === 'custom' && - ['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') + item.messageType === + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM && + ['beReCallMsg', 'reCallMsg'].includes(item.recallType || '') ) )[0] if (_msg) { - await getHistory(_msg.time, _msg.idServer) + await getHistory(_msg.createTime, _msg.messageServerId) // 滚动到加载的那条消息 - document.getElementById(_msg.idClient)?.scrollIntoView() + document.getElementById(_msg.messageClientId)?.scrollIntoView() } } } @@ -226,9 +241,9 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( }, []) const onReeditClick = useCallback( - (msg: IMMessage) => { - setInputValue(msg.attach?.oldBody || '') - const replyMsg = replyMsgsMap[msg.idClient] + (msg: V2NIMMessageForUI) => { + setInputValue(msg.oldText || '') + const replyMsg = replyMsgsMap[msg.messageClientId] replyMsg && store.msgStore.replyMsgActive(replyMsg) chatMessageInputRef.current?.input?.focus() }, @@ -236,16 +251,36 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( ) const onResend = useCallback( - async (msg: IMMessage) => { + async (msg: V2NIMMessageForUI) => { try { - await store.msgStore.resendMsgActive(msg) - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { + switch (msg.messageType) { + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE: + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_VIDEO: + await store.msgStore.sendMessageActive({ + msg, + conversationId, + progress: () => true, + sendBefore: () => { + scrollToBottom() + }, + }) + break + default: + await store.msgStore.sendMessageActive({ + msg, + conversationId, + sendBefore: () => { + scrollToBottom() + }, + }) + break + } scrollToBottom() + } catch (error) { + // } }, - [scrollToBottom, store.msgStore] + [store.msgStore, conversationId, scrollToBottom] ) const onSendText = useCallback( @@ -254,14 +289,17 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( if (onSendTextFromProps) { await onSendTextFromProps({ value, - scene, - to, + conversationType, + receiverId, }) } else { - await store.msgStore.sendTextMsgActive({ - scene, - to, - body: value, + const textMsg = nim.V2NIMMessageCreator.createTextMessage(value) + await store.msgStore.sendMessageActive({ + msg: textMsg, + conversationId, + sendBefore: () => { + scrollToBottom() + }, }) } } catch (error) { @@ -270,16 +308,30 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( scrollToBottom() } }, - [onSendTextFromProps, scene, store.msgStore, to, scrollToBottom] + [ + onSendTextFromProps, + conversationType, + store.msgStore, + receiverId, + conversationId, + scrollToBottom, + nim.V2NIMMessageCreator, + ] ) const onSendFile = useCallback( async (file: File) => { try { - await store.msgStore.sendFileMsgActive({ - scene, - to, + const fileMsg = nim.V2NIMMessageCreator.createFileMessage( file, + file.name + ) + await store.msgStore.sendMessageActive({ + msg: fileMsg, + conversationId, + sendBefore: () => { + scrollToBottom() + }, }) } catch (error) { // message.error(t('sendMsgFailedText')) @@ -287,25 +339,21 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( scrollToBottom() } }, - [scene, store.msgStore, to, scrollToBottom] + [store.msgStore, conversationId, scrollToBottom, nim.V2NIMMessageCreator] ) const onSendImg = useCallback( - async (file: File, randomId?: string) => { + async (file: File) => { try { const previewImg = await getImgDataUrl(file) - await store.msgStore.sendImageMsgActive({ - scene, - to, - file, + const imgMsg = nim.V2NIMMessageCreator.createImageMessage(file) + await store.msgStore.sendMessageActive({ + msg: imgMsg, + conversationId, previewImg, - randomId, - onUploadStart(task, taskId) { + progress: () => true, + sendBefore: () => { scrollToBottom() - addTask(taskId, task) - }, - onUploadDone(taskId) { - removeTask(taskId) }, }) } catch (error) { @@ -314,25 +362,21 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( scrollToBottom() } }, - [scene, store.msgStore, to, scrollToBottom] + [store.msgStore, conversationId, scrollToBottom, nim.V2NIMMessageCreator] ) const onSendVideo = useCallback( - async (file: File, randomId?: string) => { + async (file: File) => { try { const previewImg = await getVideoFirstFrameDataUrl(file) - await store.msgStore.sendVideoMsgActive({ - scene, - to, - file, + const videoMsg = nim.V2NIMMessageCreator.createVideoMessage(file) + await store.msgStore.sendMessageActive({ + msg: videoMsg, + conversationId, previewImg, - randomId, - onUploadStart(task, taskId) { + progress: () => true, + sendBefore: () => { scrollToBottom() - addTask(taskId, task) - }, - onUploadDone(taskId) { - removeTask(taskId) }, }) } catch (error) { @@ -341,15 +385,15 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( scrollToBottom() } }, - [scene, store.msgStore, to, scrollToBottom] + [store.msgStore, conversationId, scrollToBottom, nim.V2NIMMessageCreator] ) const onRemoveReplyMsg = useCallback(() => { - replyMsg && store.msgStore.removeReplyMsgActive(replyMsg.sessionId) + replyMsg && store.msgStore.removeReplyMsgActive(replyMsg.conversationId) }, [replyMsg, store.msgStore]) const onMessageAction = useCallback( - async (key: MenuItemKey, msg: IMMessage) => { + async (key: MenuItemKey, msg: V2NIMMessageForUI) => { const msgOperMenuItem = msgOperMenu?.find((item) => item.key === key) if (msgOperMenuItem?.onClick) { return msgOperMenuItem?.onClick(msg) @@ -362,7 +406,7 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( await store.msgStore.reCallMsgActive(msg) break case 'reply': - await store.msgStore.replyMsgActive(msg) + store.msgStore.replyMsgActive(msg) chatMessageInputRef.current?.input?.focus() break case 'forward': @@ -394,15 +438,7 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( resetSettingState() message.success(t('createTeamSuccessText')) } catch (error: any) { - switch (error?.code) { - // 无权限 - case 802: - message.error(t('noPermission')) - break - default: - message.error(t('createTeamFailedText')) - break - } + message.error(t('createTeamFailedText')) } }, [store.teamStore, t] @@ -434,11 +470,16 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( useEffect(() => { const notMyMsgs = msgs - .filter((item) => item.from !== myUser.account) - .filter((item) => !!item.idServer) + .filter((item) => item.senderId !== myUser.accountId) + .filter((item) => !!item.messageServerId) .filter((item) => // 以下这些类型的消息不需要发送已读未读 - ['notification', 'tip', 'robot', 'g2'].every((j) => j !== item.type) + [ + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION, + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TIPS, + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_ROBOT, + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL, + ].every((j) => j !== item.messageType) ) const visibleChangeHandler = (params: { @@ -448,7 +489,7 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( if (params.visible) { // 发送已读 const msg = notMyMsgs.find( - (item) => item.idClient === params.target.id + (item) => item.messageClientId === params.target.id ) if (msg) { store.msgStore.sendMsgReceiptActive(msg).finally(() => { @@ -460,7 +501,7 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( const handler = (isObserve: boolean) => { notMyMsgs.forEach((item) => { - const target = document.getElementById(item.idClient) + const target = document.getElementById(item.messageClientId) if (target) { if (isObserve) { visibilityObserver.observe(target) @@ -482,7 +523,7 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( return () => { handler(false) } - }, [store.msgStore, msgs, visibilityObserver, myUser.account]) + }, [store.msgStore, msgs, visibilityObserver, myUser.accountId]) useEffect(() => { return () => { @@ -494,33 +535,33 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( useEffect(() => { resetState() scrollToBottom() - }, [to, resetState, scrollToBottom]) + }, [receiverId, resetState, scrollToBottom]) // 切换会话时,如果内存中除了撤回消息的其他消息小于10条(差不多一屏幕),需要拉取历史消息 useEffect(() => { - if ( - store.msgStore - .getMsg(sessionId) - .filter( - (item) => - !['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') - ).length < 10 - ) { + const memoryMsgs = conversationId + ? store.msgStore + .getMsg(conversationId) + .filter( + (item) => + !['beReCallMsg', 'reCallMsg'].includes(item.recallType || '') + ) + : [] + + if (memoryMsgs.length < 10) { getHistory(Date.now()).then((res) => { scrollToBottom() - if (session && !session.lastMsg && res && res[0]) { - store.sessionStore.addSession([{ ...session, lastMsg: res[0] }]) - } + // TODO 考虑以下这段代码是否还需要 + // if (conversation && !conversation.lastMessage && res && res[0]) { + // store.conversationStore.addConversation([ + // { ...conversation, lastMessage: res[0] }, + // ]) + // } }) + } else { + scrollToBottom() } - }, [ - store.msgStore, - store.sessionStore, - session, - sessionId, - getHistory, - scrollToBottom, - ]) + }, [store.msgStore, conversationId, getHistory, scrollToBottom]) // 处理消息 useEffect(() => { @@ -531,25 +572,27 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( from: string to: string idServer: string + idClient: string time: number }> = [] - const idClients: Record<string, string> = {} + const messageClientIds: Record<string, string> = {} msgs.forEach((msg) => { - if (msg.ext) { + if (msg.serverExtension) { try { - const { yxReplyMsg } = JSON.parse(msg.ext) + const { yxReplyMsg } = JSON.parse(msg.serverExtension) if (yxReplyMsg) { const replyMsg = msgs.find( - (item) => item.idClient === yxReplyMsg.idClient + (item) => item.messageClientId === yxReplyMsg.idClient ) if (replyMsg) { - replyMsgsMap[msg.idClient] = replyMsg + replyMsgsMap[msg.messageClientId] = replyMsg } else { - replyMsgsMap[msg.idClient] = 'noFind' - const { scene, from, to, idServer, time } = yxReplyMsg - if (scene && from && to && idServer && time) { - reqMsgs.push({ scene, from, to, idServer, time }) - idClients[idServer] = msg.idClient + replyMsgsMap[msg.messageClientId] = 'noFind' + const { scene, from, to, idServer, idClient, time } = + yxReplyMsg + if (scene && from && to && idServer && idClient && time) { + reqMsgs.push({ scene, from, to, idServer, idClient, time }) + messageClientIds[idServer] = msg.messageClientId } } } @@ -557,10 +600,26 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( } }) if (reqMsgs.length > 0) { - store.msgStore.getMsgByIdServerActive({ reqMsgs }).then((res) => { + nim.V2NIMMessageService.getMessageListByRefers( + reqMsgs.map((item) => ({ + senderId: item.from, + receiverId: item.to, + messageClientId: item.idClient, + messageServerId: item.idServer, + createTime: item.time, + conversationType: + item.scene === 'p2p' + ? V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P + : V2NIMConst.V2NIMConversationType + .V2NIM_CONVERSATION_TYPE_TEAM, + conversationId: nim.V2NIMConversationIdUtil.p2pConversationId( + item.to + ), + })) + ).then((res) => { res.forEach((item) => { - if (item.idServer) { - replyMsgsMap[idClients[item.idServer]] = item + if (item.messageServerId) { + replyMsgsMap[messageClientIds[item.messageServerId]] = item } }) setReplyMsgsMap({ ...replyMsgsMap }) @@ -569,11 +628,14 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( setReplyMsgsMap({ ...replyMsgsMap }) } } - }, [msgs, store]) + }, [msgs, store, nim.V2NIMMessageService, nim.V2NIMConversationIdUtil]) useLayoutEffect(() => { - const onMsg = (msg: IMMessage) => { - if (messageListContainerDomRef.current && msg.sessionId === sessionId) { + const onMsg = (msg: V2NIMMessage[]) => { + if ( + messageListContainerDomRef.current && + msg[0].conversationId === conversationId + ) { // 当收到消息时,如果已经往上滚动了,是不需要滚动到最底部的 if ( messageListContainerDomRef.current.scrollTop < @@ -588,18 +650,18 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( } } - nim.on('msg', onMsg) + nim.V2NIMMessageService.on('onReceiveMessages', onMsg) return () => { - nim.off('msg', onMsg) + nim.V2NIMMessageService.off('onReceiveMessages', onMsg) } - }, [nim, sessionId]) + }, [nim, conversationId, scrollToBottom]) - return session ? ( + return conversation ? ( <div className={`${prefix}-wrap`}> <div ref={settingDrawDomRef} className={`${prefix}-content`}> {renderHeader ? ( - renderHeader(session) + renderHeader(conversation) ) : ( <ChatHeader prefix={prefix} @@ -616,8 +678,8 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( } avatar={ <ComplexAvatarContainer - account={to} - canClick={to !== myUser.account} + account={receiverId} + canClick={receiverId !== myUser.accountId} prefix={commonPrefix} /> } @@ -630,18 +692,15 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( msgs={msgs} msgOperMenu={msgOperMenu} replyMsgsMap={replyMsgsMap} - member={user} + receiverId={receiverId} noMore={noMore} loadingMore={loadingMore} - myAccount={myUser?.account || ''} receiveMsgBtnVisible={receiveMsgBtnVisible} - msgReceiptTime={session?.msgReceiptTime} + msgReceiptTime={conversation?.msgReceiptTime} onReceiveMsgBtnClick={scrollToBottom} - onResend={onResend} - onSendImg={onSendImg} - onSendVideo={onSendVideo} onMessageAction={onMessageAction} onReeditClick={onReeditClick} + onResend={onResend} onScroll={onMsgListScrollHandler} renderP2pCustomMessage={renderP2pCustomMessage} renderMessageAvatar={renderMessageAvatar} @@ -655,16 +714,14 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( prefix={prefix} placeholder={ renderP2pInputPlaceHolder - ? renderP2pInputPlaceHolder(session) + ? renderP2pInputPlaceHolder(conversation) : `${t('sendToText')} ${userNickOrAccount}${t('sendUsageText')}` } replyMsg={replyMsg} - scene={scene} - to={to} + conversationType={conversationType} + receiverId={receiverId} actions={actions} inputValue={inputValue} - uploadImageLoading={store.uiStore.uploadImageLoading} - uploadFileLoading={store.uiStore.uploadFileLoading} setInputValue={setInputValue} onSendText={onSendText} onSendFile={onSendFile} @@ -681,8 +738,8 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( > <ChatP2pSetting alias={user.alias || ''} - account={user.account || ''} - nick={user.nick || ''} + account={user.accountId || ''} + nick={user.name || ''} onCreateGroupClick={() => { setGroupCreateVisible(true) }} @@ -707,7 +764,7 @@ const P2pChatContainer: React.FC<P2pChatContainerProps> = observer( /> <ChatForwardModal visible={!!forwardMessage} - msg={forwardMessage!} + msg={forwardMessage} onSend={handleForwardModalSend} onCancel={handleForwardModalClose} prefix={prefix} 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 bacb10e..4f800bf 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 @@ -21,56 +21,64 @@ import { useStateContext, useTranslation, CrudeAvatar } from '../../common' import { LeftOutlined } from '@ant-design/icons' import { Action, ChatSettingActionItem, MsgOperMenuItem } from '../Container' import ChatTeamSetting, { HistoryStack } from '../components/ChatTeamSetting' -import { Session } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/SessionServiceInterface' import { debounce, VisibilityObserver } from '@xkit-yx/utils' -import { - IMMessage, - TMsgScene, -} from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' import { MenuItemKey, AvatarMenuItem } from '../components/ChatMessageItem' import { message } from 'antd' import { ALLOW_AT, TAllowAt } from '../../constant' -import { storeConstants } from '@xkit-yx/im-store' -import { - Team, - TeamMember, - UpdateMyMemberInfoOptions, -} from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { storeConstants } from '@xkit-yx/im-store-v2' import { observer } from 'mobx-react' import { GroupItemProps } from '../components/ChatTeamSetting/GroupItem' import ChatForwardModal from '../components/ChatForwardModal' import { MentionedMember } from '../components/ChatMessageInput/ChatMentionMemberList' import GroupTransferModal from '../components/ChatGroupTransferModal' import { getImgDataUrl, getVideoFirstFrameDataUrl } from '../../utils' -import { addTask, removeTask } from '../../uploadingTask' +import { + V2NIMTeam, + V2NIMTeamMember, + V2NIMUpdatedTeamInfo, + V2NIMUpdateSelfMemberInfoParams, +} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { + V2NIMConversationType, + V2NIMConversation, +} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types' +import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMMessageNotificationAttachment } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMMessageService' export interface TeamChatContainerProps { - scene: TMsgScene - to: string + conversationType: V2NIMConversationType + receiverId: string settingActions?: ChatSettingActionItem[] actions?: Action[] msgOperMenu?: MsgOperMenuItem[] onSendText?: (data: { value: string - scene: TMsgScene - to: string + conversationType: V2NIMConversationType + receiverId: string }) => Promise<void> afterTransferTeam?: (teamId: string) => Promise<void> renderTeamCustomMessage?: ( options: RenderTeamCustomMessageOptions ) => JSX.Element | null | undefined - renderHeader?: (session: Session) => JSX.Element + renderHeader?: (conversation: V2NIMConversation) => JSX.Element renderTeamInputPlaceHolder?: (params: { - session: Session + conversation: V2NIMConversation mute: boolean }) => string renderTeamMemberItem?: ( params: GroupItemProps ) => JSX.Element | null | undefined - renderMessageAvatar?: (msg: IMMessage) => JSX.Element | null | undefined - renderMessageName?: (msg: IMMessage) => JSX.Element | null | undefined - renderMessageOuterContent?: (msg: IMMessage) => JSX.Element | null | undefined - renderMessageInnerContent?: (msg: IMMessage) => JSX.Element | null | undefined + renderMessageAvatar?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined + renderMessageName?: (msg: V2NIMMessageForUI) => JSX.Element | null | undefined + renderMessageOuterContent?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined + renderMessageInnerContent?: ( + msg: V2NIMMessageForUI + ) => JSX.Element | null | undefined prefix?: string commonPrefix?: string @@ -78,8 +86,8 @@ export interface TeamChatContainerProps { const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( ({ - scene, - to, + conversationType, + receiverId, settingActions, actions, msgOperMenu, @@ -101,94 +109,106 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( const { t } = useTranslation() - const sessionId = `${scene}-${to}` + const conversationId = + nim.V2NIMConversationIdUtil.teamConversationId(receiverId) - const session = store.sessionStore.sessions.get(sessionId) + const conversation = + store.conversationStore.conversations.get(conversationId) - const msgs = store.msgStore.getMsg(sessionId) + const msgs = store.msgStore.getMsg(conversationId) - const replyMsg = store.msgStore.replyMsgs.get(sessionId) + const replyMsg = store.msgStore.replyMsgs.get(conversationId) - const team: Team = store.teamStore.teams.get(to) || { - teamId: to, - type: 'normal', + const team: V2NIMTeam = store.teamStore.teams.get(receiverId) || { + teamId: receiverId, + teamType: V2NIMConst.V2NIMTeamType.V2NIM_TEAM_TYPE_ADVANCED, name: '', avatar: '', intro: '', announcement: '', - joinMode: 'noVerify', - beInviteMode: 'noVerify', - inviteMode: 'manager', - updateTeamMode: 'manager', - updateExtMode: 'manager', - owner: '', - level: 0, - memberNum: 0, - memberUpdateTime: Date.now(), + joinMode: V2NIMConst.V2NIMTeamJoinMode.V2NIM_TEAM_JOIN_MODE_FREE, + agreeMode: V2NIMConst.V2NIMTeamAgreeMode.V2NIM_TEAM_AGREE_MODE_NO_AUTH, + inviteMode: V2NIMConst.V2NIMTeamInviteMode.V2NIM_TEAM_INVITE_MODE_MANAGER, + updateInfoMode: + V2NIMConst.V2NIMTeamUpdateInfoMode.V2NIM_TEAM_UPDATE_INFO_MODE_MANAGER, + updateExtensionMode: + V2NIMConst.V2NIMTeamUpdateExtensionMode + .V2NIM_TEAM_UPDATE_EXTENSION_MODE_MANAGER, + ownerAccountId: '', + memberCount: 0, createTime: Date.now(), updateTime: Date.now(), - ext: '', - serverExt: '', - valid: false, - validToCurrentUser: false, - mute: false, - muteType: 'none', + serverExtension: '', + isValidTeam: false, + chatBannedMode: + V2NIMConst.V2NIMTeamChatBannedMode.V2NIM_TEAM_CHAT_BANNED_MODE_UNBAN, + memberLimit: 200, + messageNotifyMode: + V2NIMConst.V2NIMTeamMessageNotifyMode + .V2NIM_TEAM_MESSAGE_NOTIFY_MODE_ALL, } - const teamMembers = store.teamMemberStore.getTeamMember(to) + const teamMembers = store.teamMemberStore.getTeamMember(receiverId) const myUser = store.userStore.myUserInfo const teamNameOrTeamId = team?.name || team?.teamId || '' - const isGroupOwner = myUser?.account === team.owner + const isGroupOwner = myUser?.accountId === team.ownerAccountId const isGroupManager = teamMembers - .filter((item) => item.type === 'manager') - .some((item) => item.account === myUser?.account) - - const mentionMembers = useMemo(() => { - return teamMembers.filter( - (member) => member.account !== myUser?.account - // if (member.account !== myUser?.account) { - // member.alias = store.uiStore.getAppellation({ - // account: member.account, - // teamId: member.teamId, - // }) - // member.nickInTeam = store.uiStore.getAppellation({ - // account: member.account, - // teamId: member.teamId, - // ignoreAlias: true, - // }) - // return true - // } - // return false + .filter( + (item) => + item.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_MANAGER ) - }, [teamMembers, myUser?.account]) + .some((item) => item.accountId === myUser?.accountId) const sortedMembers = useMemo(() => { - const owner = teamMembers.filter((item) => item.type === 'owner') + const owner = teamMembers.filter( + (item) => + item.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_OWNER + ) const manager = teamMembers - .filter((item) => item.type === 'manager') + .filter( + (item) => + item.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_MANAGER + ) .sort((a, b) => a.joinTime - b.joinTime) const other = teamMembers - .filter((item) => !['owner', 'manager'].includes(item.type)) + .filter( + (item) => + item.memberRole === + V2NIMConst.V2NIMTeamMemberRole.V2NIM_TEAM_MEMBER_ROLE_NORMAL + ) .sort((a, b) => a.joinTime - b.joinTime) const _sortedMembers = [...owner, ...manager, ...other] return _sortedMembers }, [teamMembers]) + const mentionMembers = useMemo(() => { + return sortedMembers.filter( + (member) => member.accountId !== myUser?.accountId + ) + }, [sortedMembers, myUser?.accountId]) + const teamMute = useMemo(() => { - if (team.mute) { + if ( + team.chatBannedMode === + V2NIMConst.V2NIMTeamChatBannedMode + .V2NIM_TEAM_CHAT_BANNED_MODE_BANNED_NORMAL + ) { return !isGroupOwner && !isGroupManager } - return team.mute - }, [team.mute, isGroupOwner, isGroupManager]) + return false + }, [team.chatBannedMode, isGroupOwner, isGroupManager]) const allowAtAll = useMemo(() => { let ext: TAllowAt = {} try { - ext = JSON.parse(team.ext || '{}') + ext = JSON.parse(team.serverExtension || '{}') } catch (error) { // } @@ -196,13 +216,13 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( return isGroupOwner || isGroupManager } return true - }, [team.ext, isGroupOwner, isGroupManager]) + }, [team.serverExtension, isGroupOwner, isGroupManager]) const teamDefaultAddMembers = useMemo(() => { return teamMembers - .filter((item) => item.account !== myUser?.account) - .map((item) => item.account) - }, [teamMembers, myUser?.account]) + .filter((item) => item.accountId !== myUser?.accountId) + .map((item) => item.accountId) + }, [teamMembers, myUser?.accountId]) const messageListContainerDomRef = useRef<HTMLDivElement>(null) const settingDrawDomRef = useRef<HTMLDivElement>(null) @@ -212,15 +232,15 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( return new VisibilityObserver({ root: messageListContainerDomRef.current, }) - }, [to]) + }, [receiverId]) const [groupTransferModalVisible, setGroupTransferModalVisible] = useState<boolean>(false) // 以下是 UI 相关的 state,需要在切换会话时重置 - const [replyMsgsMap, setReplyMsgsMap] = useState<Record<string, IMMessage>>( - {} - ) // 回复消息的 map + const [replyMsgsMap, setReplyMsgsMap] = useState< + Record<string, V2NIMMessageForUI> + >({}) // 回复消息的 map const [inputValue, setInputValue] = useState('') const [navHistoryStack, setNavHistoryStack] = useState<HistoryStack[]>([]) const [action, setAction] = useState<ChatAction | undefined>(undefined) @@ -229,9 +249,9 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( const [groupAddMembersVisible, setGroupAddMembersVisible] = useState(false) const [receiveMsgBtnVisible, setReceiveMsgBtnVisible] = useState(false) const [settingDrawerVisible, setSettingDrawerVisible] = useState(false) - const [forwardMessage, setForwardMessage] = useState<IMMessage | undefined>( - undefined - ) + const [forwardMessage, setForwardMessage] = useState< + V2NIMMessageForUI | undefined + >(undefined) const SETTING_NAV_TITLE_MAP: { [key in ChatAction]: string } = useMemo( () => ({ @@ -267,7 +287,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( try { setLoadingMore(true) const historyMsgs = await store.msgStore.getHistoryMsgActive({ - sessionId, + conversationId, endTime, lastMsgId, limit: storeConstants.HISTORY_LIMIT, @@ -277,22 +297,32 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( setNoMore(true) } return historyMsgs - } catch (error) { + } catch (error: any) { setLoadingMore(false) - message.error(t('getHistoryMsgFailedText')) + switch (error.code) { + case 109404: + message.error(t('teamMemberNotExist')) + break + default: + message.error(t('getHistoryMsgFailedText')) + break + } } }, - [sessionId, store.msgStore, t] + [conversationId, store.msgStore, t] ) // 收消息,发消息时需要调用 - const scrollToBottom = useCallback(() => { - if (messageListContainerDomRef.current) { - messageListContainerDomRef.current.scrollTop = - messageListContainerDomRef.current.scrollHeight - } - setReceiveMsgBtnVisible(false) - }, []) + const scrollToBottom = useCallback( + debounce(() => { + if (messageListContainerDomRef.current) { + messageListContainerDomRef.current.scrollTop = + messageListContainerDomRef.current.scrollHeight + } + setReceiveMsgBtnVisible(false) + }, 300), + [] + ) const onMsgListScrollHandler = useCallback( debounce(async () => { @@ -314,14 +344,15 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( const _msg = msgs.filter( (item) => !( - item.type === 'custom' && - ['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') + item.messageType === + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM && + ['beReCallMsg', 'reCallMsg'].includes(item.recallType || '') ) )[0] if (_msg) { - await getHistory(_msg.time, _msg.idServer) + await getHistory(_msg.createTime, _msg.messageServerId) // 滚动到加载的那条消息 - document.getElementById(_msg.idClient)?.scrollIntoView() + document.getElementById(_msg.messageClientId)?.scrollIntoView() } } } @@ -356,14 +387,14 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( }, []) const onReeditClick = useCallback( - (msg: IMMessage) => { - const replyMsg = replyMsgsMap[msg.idClient] + (msg: V2NIMMessageForUI) => { + const replyMsg = replyMsgsMap[msg.messageClientId] replyMsg && store.msgStore.replyMsgActive(replyMsg) // 处理 @ 消息 - const { ext } = msg - if (ext) { + const { serverExtension } = msg + if (serverExtension) { try { - const extObj = JSON.parse(ext) + const extObj = JSON.parse(serverExtension) const yxAitMsg = extObj.yxAitMsg if (yxAitMsg) { const mentionedMembers: MentionedMember[] = [] @@ -375,13 +406,13 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( }) } else { const member = teamMembers.find( - (item) => item.account === key + (item) => item.accountId === key ) member && mentionedMembers.push({ - account: member.account, + account: member.accountId, appellation: store.uiStore.getAppellation({ - account: member.account, + account: member.accountId, teamId: member.teamId, ignoreAlias: true, }), @@ -394,23 +425,43 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( } } catch {} } - setInputValue(msg.attach?.oldBody || '') + setInputValue(msg.oldText || '') chatMessageInputRef.current?.input?.focus() }, [replyMsgsMap, store.msgStore, teamMembers, store.uiStore, t] ) const onResend = useCallback( - async (msg: IMMessage) => { + async (msg: V2NIMMessageForUI) => { try { - await store.msgStore.resendMsgActive(msg) - } catch (error) { - // message.error(t('sendMsgFailedText')) - } finally { + switch (msg.messageType) { + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE: + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_VIDEO: + await store.msgStore.sendMessageActive({ + msg, + conversationId, + progress: () => true, + sendBefore: () => { + scrollToBottom() + }, + }) + break + default: + await store.msgStore.sendMessageActive({ + msg, + conversationId, + sendBefore: () => { + scrollToBottom() + }, + }) + break + } scrollToBottom() + } catch (error) { + // } }, - [scrollToBottom, store.msgStore] + [store.msgStore, conversationId, scrollToBottom] ) const onSendText = useCallback( @@ -419,15 +470,18 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( if (onSendTextFromProps) { await onSendTextFromProps({ value, - scene, - to, + conversationType, + receiverId, }) } else { - await store.msgStore.sendTextMsgActive({ - scene, - to, - body: value, - ext, + const textMsg = nim.V2NIMMessageCreator.createTextMessage(value) + await store.msgStore.sendMessageActive({ + msg: textMsg, + conversationId, + serverExtension: ext, + sendBefore: () => { + scrollToBottom() + }, }) } } catch (error) { @@ -436,16 +490,30 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( scrollToBottom() } }, - [onSendTextFromProps, scene, store.msgStore, to, scrollToBottom] + [ + onSendTextFromProps, + conversationType, + store.msgStore, + receiverId, + conversationId, + scrollToBottom, + nim.V2NIMMessageCreator, + ] ) const onSendFile = useCallback( async (file: File) => { try { - await store.msgStore.sendFileMsgActive({ - scene, - to, + const fileMsg = nim.V2NIMMessageCreator.createFileMessage( file, + file.name + ) + await store.msgStore.sendMessageActive({ + msg: fileMsg, + conversationId, + sendBefore: () => { + scrollToBottom() + }, }) } catch (error) { // message.error(t('sendMsgFailedText')) @@ -453,25 +521,21 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( scrollToBottom() } }, - [scene, store.msgStore, to, scrollToBottom] + [store.msgStore, conversationId, scrollToBottom, nim.V2NIMMessageCreator] ) const onSendImg = useCallback( - async (file: File, randomId?: string) => { + async (file: File) => { try { const previewImg = await getImgDataUrl(file) - await store.msgStore.sendImageMsgActive({ - scene, - to, - file, + const imgMsg = nim.V2NIMMessageCreator.createImageMessage(file) + await store.msgStore.sendMessageActive({ + msg: imgMsg, + conversationId, previewImg, - randomId, - onUploadStart(task, taskId) { + progress: () => true, + sendBefore: () => { scrollToBottom() - addTask(taskId, task) - }, - onUploadDone(taskId) { - removeTask(taskId) }, }) } catch (error) { @@ -480,25 +544,21 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( scrollToBottom() } }, - [scene, store.msgStore, to, scrollToBottom] + [store.msgStore, conversationId, scrollToBottom, nim.V2NIMMessageCreator] ) const onSendVideo = useCallback( - async (file: File, randomId?: string) => { + async (file: File) => { try { const previewImg = await getVideoFirstFrameDataUrl(file) - await store.msgStore.sendVideoMsgActive({ - scene, - to, - file, + const videoMsg = nim.V2NIMMessageCreator.createVideoMessage(file) + await store.msgStore.sendMessageActive({ + msg: videoMsg, + conversationId, previewImg, - randomId, - onUploadStart(task, taskId) { + progress: () => true, + sendBefore: () => { scrollToBottom() - addTask(taskId, task) - }, - onUploadDone(taskId) { - removeTask(taskId) }, }) } catch (error) { @@ -507,15 +567,15 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( scrollToBottom() } }, - [scene, store.msgStore, to, scrollToBottom] + [store.msgStore, conversationId, scrollToBottom, nim.V2NIMMessageCreator] ) const onRemoveReplyMsg = useCallback(() => { - replyMsg && store.msgStore.removeReplyMsgActive(replyMsg.sessionId) + replyMsg && store.msgStore.removeReplyMsgActive(replyMsg.conversationId) }, [replyMsg, store.msgStore]) const onMessageAction = useCallback( - async (key: MenuItemKey, msg: IMMessage) => { + async (key: MenuItemKey, msg: V2NIMMessageForUI) => { const msgOperMenuItem = msgOperMenu?.find((item) => item.key === key) if (msgOperMenuItem?.onClick) { return msgOperMenuItem?.onClick(msg) @@ -529,18 +589,18 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( break case 'reply': const member = mentionMembers.find( - (item) => item.account === msg.from + (item) => item.accountId === msg.senderId ) member && chatMessageInputRef.current?.onAtMemberSelectHandler({ - account: member.account, + account: member.accountId, appellation: store.uiStore.getAppellation({ - account: member.account, + account: member.accountId, teamId: member.teamId, ignoreAlias: true, }), }) - await store.msgStore.replyMsgActive(msg) + store.msgStore.replyMsgActive(msg) chatMessageInputRef.current?.input?.focus() break case 'forward': @@ -554,17 +614,17 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( ) const onMessageAvatarAction = useCallback( - async (key: AvatarMenuItem, msg: IMMessage) => { + async (key: AvatarMenuItem, msg: V2NIMMessageForUI) => { switch (key) { case 'mention': const member = mentionMembers.find( - (item) => item.account === msg.from + (item) => item.accountId === msg.senderId ) member && chatMessageInputRef.current?.onAtMemberSelectHandler({ - account: member.account, + account: member.accountId, appellation: store.uiStore.getAppellation({ - account: member.account, + account: member.accountId, teamId: member.teamId, ignoreAlias: true, }), @@ -584,7 +644,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( } catch (error: any) { switch (error?.code) { // 无权限 - case 802: + case 109427: message.error(t('noPermission')) break default: @@ -599,15 +659,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( await store.teamStore.leaveTeamActive(team.teamId) message.success(t('leaveTeamSuccessText')) } catch (error: any) { - switch (error?.code) { - // 无权限 - case 802: - message.error(t('noPermission')) - break - default: - message.error(t('leaveTeamFailedText')) - break - } + message.error(t('leaveTeamFailedText')) } }, [store.teamStore, team.teamId, t]) @@ -621,7 +673,12 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( }, [afterTransferTeam, team.teamId]) const onAddMembersClick = useCallback(() => { - if (team.inviteMode === 'manager' && !isGroupOwner && !isGroupManager) { + if ( + team.inviteMode === + V2NIMConst.V2NIMTeamInviteMode.V2NIM_TEAM_INVITE_MODE_MANAGER && + !isGroupOwner && + !isGroupManager + ) { message.error(t('noPermission')) } else { setGroupAddMembersVisible(true) @@ -640,7 +697,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( } catch (error: any) { switch (error?.code) { // 无权限 - case 802: + case 109306: message.error(t('noPermission')) break default: @@ -653,17 +710,17 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( ) const onRemoveTeamMember = useCallback( - async (member: TeamMember) => { + async (member: V2NIMTeamMember) => { try { await store.teamMemberStore.removeTeamMemberActive({ teamId: team.teamId, - accounts: [member.account], + accounts: [member.accountId], }) message.success(t('removeTeamMemberSuccessText')) } catch (error: any) { switch (error?.code) { // 无权限 - case 802: + case 109306: message.error(t('noPermission')) break default: @@ -676,17 +733,17 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( ) const onUpdateTeamInfo = useCallback( - async (params: Partial<Team>) => { + async (params: V2NIMUpdatedTeamInfo) => { try { await store.teamStore.updateTeamActive({ - ...params, teamId: team.teamId, + info: params, }) message.success(t('updateTeamSuccessText')) } catch (error: any) { switch (error?.code) { // 无权限 - case 802: + case 109432: message.error(t('noPermission')) break default: @@ -699,45 +756,35 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( ) const onUpdateMyMemberInfo = useCallback( - async (params: UpdateMyMemberInfoOptions) => { - const nickTipVisible = params.nickInTeam !== void 0 - const bitConfigVisible = params.bitConfigMask !== void 0 + async (params: V2NIMUpdateSelfMemberInfoParams) => { + const nickTipVisible = params.teamNick !== void 0 try { - await store.teamMemberStore.updateMyMemberInfo(params) + await store.teamMemberStore.updateMyMemberInfoActive({ + teamId: team.teamId, + memberInfo: 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 + if (nickTipVisible) { + message.error(t('updateMyMemberNickFailed')) } } }, - [store.teamMemberStore, t] + [store.teamMemberStore, team.teamId, t] ) const onTeamMuteChange = useCallback( async (mute: boolean) => { try { - await store.teamStore.muteTeamActive({ + await store.teamStore.setTeamChatBannedActive({ teamId: team.teamId, - mute, + chatBannedMode: mute + ? V2NIMConst.V2NIMTeamChatBannedMode + .V2NIM_TEAM_CHAT_BANNED_MODE_BANNED_NORMAL + : V2NIMConst.V2NIMTeamChatBannedMode + .V2NIM_TEAM_CHAT_BANNED_MODE_UNBAN, }) message.success( mute ? t('muteAllTeamSuccessText') : t('unmuteAllTeamSuccessText') @@ -745,7 +792,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( } catch (error: any) { switch (error?.code) { // 无权限 - case 802: + case 109432: message.error(t('noPermission')) break default: @@ -790,11 +837,16 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( useEffect(() => { const notMyMsgs = msgs - .filter((item) => item.from !== myUser.account) - .filter((item) => !!item.idServer) + .filter((item) => item.senderId !== myUser.accountId) + .filter((item) => !!item.messageServerId) .filter((item) => // 以下这些类型的消息不需要发送已读未读 - ['notification', 'tip', 'robot', 'g2'].every((j) => j !== item.type) + [ + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION, + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TIPS, + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_ROBOT, + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL, + ].every((j) => j !== item.messageType) ) const visibleChangeHandler = (params: { @@ -804,17 +856,11 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( if (params.visible) { // 发送已读 const msg = notMyMsgs.find( - (item) => item.idClient === params.target.id + (item) => item.messageClientId === params.target.id ) if (msg) { store.msgStore - .sendTeamMsgReceiptActive([ - { - teamId: team.teamId, - idClient: msg.idClient, - idServer: msg.idServer ? msg.idServer : '', - }, - ]) + .sendTeamMsgReceiptActive([msg]) .catch((err) => { // 忽略这个报错 }) @@ -827,7 +873,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( const handler = (isObserve: boolean) => { notMyMsgs.forEach((item) => { - const target = document.getElementById(item.idClient) + const target = document.getElementById(item.messageClientId) if (target) { if (isObserve) { visibilityObserver.observe(target) @@ -849,7 +895,13 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( return () => { handler(false) } - }, [store.msgStore, msgs, visibilityObserver, team.teamId, myUser.account]) + }, [ + store.msgStore, + msgs, + visibilityObserver, + team.teamId, + myUser.accountId, + ]) useEffect(() => { return () => { @@ -861,34 +913,66 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( useEffect(() => { resetState() scrollToBottom() - store.teamStore.getTeamActive(to) - store.teamMemberStore.getTeamMemberActive(to) - }, [store.teamStore, store.teamMemberStore, to, resetState, scrollToBottom]) + store.teamStore.getTeamActive(receiverId).catch((err) => { + console.warn('获取群组失败:', err.toString()) + }) + store.teamMemberStore + .getTeamMemberActive({ + teamId: receiverId, + type: 1, + queryOption: { + limit: Math.max(team.memberLimit, 200), + roleQueryType: 0, + }, + }) + .catch((err) => { + console.warn('获取群组成员失败:', err.toString()) + }) + }, [ + team.memberLimit, + store.teamStore, + store.teamMemberStore, + receiverId, + resetState, + scrollToBottom, + ]) // 切换会话时,如果内存中除了撤回消息的其他消息小于10条(差不多一屏幕),需要拉取历史消息 useEffect(() => { - if ( - store.msgStore - .getMsg(sessionId) - .filter( - (item) => - !['beReCallMsg', 'reCallMsg'].includes(item.attach?.type || '') - ).length < 10 - ) { + const memoryMsgs = conversationId + ? store.msgStore + .getMsg(conversationId) + .filter( + (item) => + !['beReCallMsg', 'reCallMsg'].includes(item.recallType || '') + ) + : [] + + if (memoryMsgs.length < 10) { getHistory(Date.now()).then((res) => { scrollToBottom() - if (session && !session.lastMsg && res && res[0]) { - store.sessionStore.addSession([{ ...session, lastMsg: res[0] }]) - } + // TODO 考虑以下这段代码是否还需要 + // if (conversation && !conversation.lastMessage && res && res[0]) { + // store.conversationStore.addConversation([ + // { ...conversation, lastMessage: res[0] }, + // ]) + // } }) + } else { + // 获取自己发出去的消息 + const myMsgs = memoryMsgs.filter( + (item) => item.senderId === myUser.accountId + ) + // 获取群组已读未读数 + store.msgStore.getTeamMsgReadsActive(myMsgs, conversationId) + scrollToBottom() } }, [ store.msgStore, - store.sessionStore, - session, - sessionId, + conversationId, getHistory, scrollToBottom, + myUser.accountId, ]) // 处理消息 @@ -900,25 +984,27 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( from: string to: string idServer: string + idClient: string time: number }> = [] - const idClients: string[] = [] + const messageClientIds: string[] = [] msgs.forEach((msg) => { - if (msg.ext) { + if (msg.serverExtension) { try { - const { yxReplyMsg } = JSON.parse(msg.ext) + const { yxReplyMsg } = JSON.parse(msg.serverExtension) if (yxReplyMsg) { const replyMsg = msgs.find( - (item) => item.idClient === yxReplyMsg.idClient + (item) => item.messageClientId === yxReplyMsg.idClient ) if (replyMsg) { - replyMsgsMap[msg.idClient] = replyMsg + replyMsgsMap[msg.messageClientId] = replyMsg } else { - replyMsgsMap[msg.idClient] = 'noFind' - const { scene, from, to, idServer, time } = yxReplyMsg - if (scene && from && to && idServer && time) { - reqMsgs.push({ scene, from, to, idServer, time }) - idClients.push(msg.idClient) + replyMsgsMap[msg.messageClientId] = 'noFind' + const { scene, from, to, idServer, idClient, time } = + yxReplyMsg + if (scene && from && to && idServer && idClient && time) { + reqMsgs.push({ scene, from, to, idServer, idClient, time }) + messageClientIds.push(msg.messageClientId) } } } @@ -926,9 +1012,25 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( } }) if (reqMsgs.length > 0) { - store.msgStore.getMsgByIdServerActive({ reqMsgs }).then((res) => { + nim.V2NIMMessageService.getMessageListByRefers( + reqMsgs.map((item) => ({ + senderId: item.from, + receiverId: item.to, + messageClientId: item.idClient, + messageServerId: item.idServer, + createTime: item.time, + conversationType: + item.scene === 'p2p' + ? V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P + : V2NIMConst.V2NIMConversationType + .V2NIM_CONVERSATION_TYPE_TEAM, + conversationId: nim.V2NIMConversationIdUtil.teamConversationId( + item.to + ), + })) + ).then((res) => { res.forEach((item, index) => { - replyMsgsMap[idClients[index]] = item + replyMsgsMap[messageClientIds[index]] = item }) setReplyMsgsMap({ ...replyMsgsMap }) }) @@ -936,11 +1038,20 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( setReplyMsgsMap({ ...replyMsgsMap }) } } - }, [msgs, store, team.teamId]) + }, [ + msgs, + store, + team.teamId, + nim.V2NIMMessageService, + nim.V2NIMConversationIdUtil, + ]) useLayoutEffect(() => { - const onMsg = (msg: IMMessage) => { - if (messageListContainerDomRef.current && msg.sessionId === sessionId) { + const onMsg = (msg: V2NIMMessageForUI[]) => { + if ( + messageListContainerDomRef.current && + msg[0].conversationId === conversationId + ) { // 当收到消息时,如果已经往上滚动了,是不需要滚动到最底部的 if ( messageListContainerDomRef.current.scrollTop < @@ -955,88 +1066,50 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( } } - nim.on('msg', onMsg) + nim.V2NIMMessageService.on('onReceiveMessages', onMsg) return () => { - nim.off('msg', onMsg) + nim.V2NIMMessageService.off('onReceiveMessages', onMsg) } - }, [nim, sessionId]) + }, [nim, conversationId, scrollToBottom]) useEffect(() => { - // const onDismissTeam = (data: { teamId: string }) => { - // const _sessionId = `team-${data.teamId}` - // if (_sessionId === sessionId) { - // message.warning(t('onDismissTeamText')) - // } - // } - // const onAddTeamMembers = (data: { - // team: Team - // // 以下两个参数是增量 - // accounts: string[] - // members: TeamMember[] - // }) => { - // const _sessionId = `team-${data.team.teamId}` - // if (_sessionId === sessionId) { - // const nicks = data.members.map( - // (item) => item.nickInTeam || item.account - // ) - // message.info(`${nicks.join(',')}${t('enterTeamText')}`) - // } - // } - // const onRemoveTeamMembers = (data: { - // team: Team - // accounts: string[] - // }) => { - // const _sessionId = `team-${data.team.teamId}` - // if (_sessionId === sessionId) { - // if (data.accounts.includes(myUser.account)) { - // message.warning(t('onRemoveTeamText')) - // } else { - // const _tms = store.teamMemberStore.teamMembers.get(data.team.teamId) - // let nicks: string[] = [] - // if (_tms) { - // nicks = data.accounts - // .map((item) => { - // const _t = _tms.get(item) - // if (_t) { - // return _t.nickInTeam || _t.account - // } - // return '' - // }) - // .filter((item) => !!item) - // } - // message.info(`${nicks.join(',')}${t('leaveTeamText')}`) - // } - // } - // } - // 根据 onMsg 处理提示 - const onMsgToast = (msg: IMMessage) => { - if (msg.sessionId === sessionId && msg.type === 'notification') { - switch (msg.attach?.type) { + const onMsgToast = (msgs: V2NIMMessageForUI[]) => { + const msg = msgs[0] + if ( + msg.conversationId === conversationId && + msg.messageType === + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION + ) { + const attachment = + msg.attachment as V2NIMMessageNotificationAttachment + switch (attachment?.type) { // 主动离开群聊 - case 'leaveTeam': { - if (msg.from === myUser.account) { + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_LEAVE: { + if (msg.senderId === myUser.accountId) { message.success(t('leaveTeamSuccessText')) } else { message.info( `${store.uiStore.getAppellation({ - account: msg.from, - teamId: msg.to, + account: msg.senderId, + teamId: msg.receiverId, })}${t('leaveTeamText')}` ) } break } // 踢出群聊 - case 'removeTeamMembers': { - if (msg.attach?.accounts.includes(myUser.account)) { + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_KICK: { + if ((attachment?.targetIds || []).includes(myUser.accountId)) { message.warning(t('onRemoveTeamText')) } else { - const nicks = msg.attach?.accounts.map((item) => + const nicks = (attachment?.targetIds || []).map((item) => store.uiStore.getAppellation({ account: item, - teamId: msg.to, + teamId: msg.receiverId, }) ) message.info(`${nicks.join(',')}${t('leaveTeamText')}`) @@ -1044,28 +1117,35 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( break } // 解散群聊 - case 'dismissTeam': + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_DISMISS: message.warning(t('onDismissTeamText')) break // 有人主动加入群聊 - case 'passTeamApply': + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_APPLY_PASS: // 邀请加入群聊对方同意 - case 'acceptTeamInvite': + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_INVITE_ACCEPT: { - if (msg.from === myUser.account) { + if (msg.senderId === myUser.accountId) { message.info( `${store.uiStore.getAppellation({ - account: msg.from, - teamId: msg.to, + account: msg.senderId, + teamId: msg.receiverId, })}${t('enterTeamText')}` ) } } break // 邀请加入群聊无需验证 - case 'addTeamMembers': { - const nicks = msg.attach?.accounts.map((item) => - store.uiStore.getAppellation({ account: item, teamId: msg.to }) + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_INVITE: { + const nicks = (attachment?.targetIds || []).map((item) => + store.uiStore.getAppellation({ + account: item, + teamId: msg.receiverId, + }) ) message.info(`${nicks.join(',')}${t('enterTeamText')}`) break @@ -1074,29 +1154,23 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( } } - nim.on('msg', onMsgToast) - // nim.on('dismissTeam', onDismissTeam) - // nim.on('addTeamMembers', onAddTeamMembers) - // nim.on('removeTeamMembers', onRemoveTeamMembers) + nim.V2NIMMessageService.on('onReceiveMessages', onMsgToast) return () => { - nim.off('msg', onMsgToast) - // nim.off('dismissTeam', onDismissTeam) - // nim.off('addTeamMembers', onAddTeamMembers) - // nim.off('removeTeamMembers', onRemoveTeamMembers) + nim.V2NIMMessageService.off('onReceiveMessages', onMsgToast) } - }, [nim, sessionId, myUser.account, store.uiStore, t]) + }, [nim, conversationId, myUser.accountId, store.uiStore, t]) - return session ? ( + return conversation ? ( <div className={`${prefix}-wrap`}> <div ref={settingDrawDomRef} className={`${prefix}-content`}> {renderHeader ? ( - renderHeader(session) + renderHeader(conversation) ) : ( <ChatHeader prefix={prefix} title={teamNameOrTeamId} - subTitle={`(${teamMembers.length} 人)`} + subTitle={`(${teamMembers.length} ${t('personUnit')})`} avatar={ <CrudeAvatar account={team.teamId} @@ -1116,12 +1190,9 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( members={teamMembers} noMore={noMore} loadingMore={loadingMore} - myAccount={myUser?.account || ''} receiveMsgBtnVisible={receiveMsgBtnVisible} onReceiveMsgBtnClick={scrollToBottom} onResend={onResend} - onSendImg={onSendImg} - onSendVideo={onSendVideo} onMessageAction={onMessageAction} onMessageAvatarAction={onMessageAvatarAction} onReeditClick={onReeditClick} @@ -1140,7 +1211,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( placeholder={ renderTeamInputPlaceHolder ? renderTeamInputPlaceHolder({ - session: session, + conversation, mute: teamMute, }) : teamMute @@ -1149,14 +1220,12 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( } replyMsg={replyMsg} mentionMembers={mentionMembers} - scene={scene} - to={to} + conversationType={conversationType} + receiverId={receiverId} actions={actions} inputValue={inputValue} mute={teamMute} allowAtAll={allowAtAll} - uploadImageLoading={store.uiStore.uploadImageLoading} - uploadFileLoading={store.uiStore.uploadFileLoading} setInputValue={setInputValue} onRemoveReplyMsg={onRemoveReplyMsg} onSendText={onSendText} @@ -1174,7 +1243,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( <ChatTeamSetting members={sortedMembers} team={team} - myAccount={myUser?.account || ''} + myAccount={myUser?.accountId || ''} isGroupManager={isGroupManager} isGroupOwner={isGroupOwner} navHistoryStack={navHistoryStack} @@ -1212,7 +1281,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( /> <ChatForwardModal visible={!!forwardMessage} - msg={forwardMessage!} + msg={forwardMessage} onSend={handleForwardModalSend} onCancel={handleForwardModalClose} prefix={prefix} @@ -1223,7 +1292,7 @@ const TeamChatContainer: React.FC<TeamChatContainerProps> = observer( members={sortedMembers} onOk={handleTransferTeam} onCancel={handleGroupActionCancel} - teamId={to} + teamId={receiverId} /> </div> ) : null 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 87f59e3..20feca8 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,18 +1,25 @@ import React, { useEffect, useRef, useState } from 'react' -import { Image, Popover, Progress } from 'antd' +import { Image, Popover, Progress, message } from 'antd' import reactStringReplace from 'react-string-replace' import CommonIcon from '../CommonIcon' import { getFileType, parseFileSize, addUrlSearch } from '@xkit-yx/utils' -import { handleEmojiTranslate } from '../../../utils' -import { IMMessage } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' +import { handleEmojiTranslate, logger } from '../../../utils' +import { + V2NIMMessage, + V2NIMMessageImageAttachment, + V2NIMMessageFileAttachment, + V2NIMMessageAudioAttachment, + V2NIMMessageLocationAttachment, + V2NIMMessageVideoAttachment, + V2NIMMessageNotificationAttachment, +} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMMessageService' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' import { useTranslation, useStateContext } from '../../index' -import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' 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' -import { abortTask } from '../../../uploadingTask' +import { V2NIMMessageForUI } from '@xkit-yx/im-store-v2/dist/types/types' +import { V2NIMConst } from 'nim-web-sdk-ng' // 对话框中要展示的文件icon标识 const fileIconMap = { @@ -29,8 +36,8 @@ const fileIconMap = { export interface IParseSessionProps { prefix?: string - msg: IMMessage - replyMsg?: IMMessage + msg: V2NIMMessageForUI + replyMsg?: V2NIMMessage } export const pauseAllAudio = (): HTMLAudioElement => { @@ -60,35 +67,43 @@ export const pauseAllVideo = (): void => { export const ParseSession: React.FC<IParseSessionProps> = observer( ({ prefix = 'common', msg, replyMsg }) => { const _prefix = `${prefix}-parse-session` - const { store, localOptions } = useStateContext() - // const imagePreview = useRef<HTMLDivElement | null>(null) + const { nim, store, localOptions } = useStateContext() const { t } = useTranslation() const locationDomRef = useRef<HTMLDivElement | null>(null) const audioContainerRef = useRef<HTMLDivElement>(null) const notSupportMessageText = t('notSupportMessageText') - // 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('') + const myAccount = store.userStore.myUserInfo.accountId useEffect(() => { - if (msg.type === 'image' && msg.attach && msg.attach.url) { - const url = `${msg.attach.url}?download=${msg.attach.name}` + if ( + msg.messageType === + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE && + msg.attachment && + (msg.attachment as V2NIMMessageImageAttachment).url + ) { + const url = `${ + (msg.attachment as V2NIMMessageImageAttachment).url + }?download=${(msg.attachment as V2NIMMessageImageAttachment).name}` getBlobImg(url).then((blobUrl) => { setImgUrl(blobUrl) }) } - }, [msg.attach, msg.type]) + }, [msg.attachment, msg.messageType]) useEffect(() => { if ( replyMsg && - replyMsg.type === 'image' && - replyMsg.attach && - replyMsg.attach.url + replyMsg.messageType === + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE && + replyMsg.attachment && + (replyMsg.attachment as V2NIMMessageImageAttachment).url ) { - const url = `${replyMsg.attach.url}?download=${replyMsg.attach.name}` + const url = `${ + (replyMsg.attachment as V2NIMMessageImageAttachment).url + }?download=${(replyMsg.attachment as V2NIMMessageImageAttachment).name}` getBlobImg(url).then((blobUrl) => { setReplyImgUrl(blobUrl) }) @@ -97,50 +112,68 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( let animationFlag = false - const teamId = scene === 'team' ? to : '' + const teamId = + msg.conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM + ? nim.V2NIMConversationIdUtil.parseConversationTargetId( + msg.conversationId + ) + : '' const { EMOJI_ICON_MAP_CONFIG, INPUT_EMOJI_SYMBOL_REG } = handleEmojiTranslate(t) - const renderCustomText = (msg: IMMessage) => { - const { body, idClient, ext } = msg + const renderCustomText = (msg: V2NIMMessageForUI) => { + const { text, messageClientId, serverExtension } = msg - let text = reactStringReplace(body, /(https?:\/\/\S+)/gi, (match, i) => ( - <a key={idClient + match + i} href={match} target="_blank"> - {match} - </a> - )) - text = reactStringReplace(text, INPUT_EMOJI_SYMBOL_REG, (match, i) => { - return ( - <CommonIcon - key={idClient + match + i} - className={`${_prefix}-emoji-icon`} - type={EMOJI_ICON_MAP_CONFIG[match]} - /> + let finalText = reactStringReplace( + text, + /(https?:\/\/\S+)/gi, + (match, i) => ( + <a key={messageClientId + match + i} href={match} target="_blank"> + {match} + </a> ) - }) - if (ext) { + ) + finalText = reactStringReplace( + finalText, + INPUT_EMOJI_SYMBOL_REG, + (match, i) => { + return ( + <CommonIcon + key={messageClientId + match + i} + className={`${_prefix}-emoji-icon`} + type={EMOJI_ICON_MAP_CONFIG[match]} + /> + ) + } + ) + if (serverExtension) { try { - const extObj = JSON.parse(ext) + const extObj = JSON.parse(serverExtension) const yxAitMsg = extObj.yxAitMsg if (yxAitMsg && localOptions.needMention) { Object.keys(yxAitMsg).forEach((key) => { const item = yxAitMsg[key] - text = reactStringReplace(text, item.text, (match, i) => { - return ( - <span - key={idClient + match + i} - className={`${_prefix}-mention`} - > - {match} - </span> - ) - }) + finalText = reactStringReplace( + finalText, + item.text, + (match, i) => { + return ( + <span + key={messageClientId + match + i} + className={`${_prefix}-mention`} + > + {match} + </span> + ) + } + ) }) } } catch {} } - return <div className={`${_prefix}-text-wrapper`}>{text}</div> + return <div className={`${_prefix}-text-wrapper`}>{finalText}</div> } const playAudioAnimation = () => { @@ -161,9 +194,8 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( handler() } - const renderUploadMsg = (msg: IMMessage) => { - // @ts-ignore - const { uploadProgress, previewImg, idClient } = msg + const renderUploadMsg = (msg: V2NIMMessageForUI) => { + const { uploadProgress, previewImg, sendingState } = msg return ( <div className={`${_prefix}-upload-container`}> @@ -174,33 +206,46 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( 'https://yx-web-nosdn.netease.im/common/33d3e1fa8de771277ea4466564ef37aa/emptyImg.png' } /> - <div className={`${_prefix}-upload-mask`}> - <div - className={`${_prefix}-upload-progress`} - onClick={() => { - abortTask(idClient) - }} - > - <Progress - type="circle" - status="exception" - percent={uploadProgress || 0} - width={40} - strokeColor="#899095" - trailColor="rgba(0,0,0,0.5)" - /> + {sendingState === + V2NIMConst.V2NIMMessageSendingState + .V2NIM_MESSAGE_SENDING_STATE_SENDING && + uploadProgress !== void 0 && + uploadProgress < 100 ? ( + <div className={`${_prefix}-upload-mask`}> + <div + className={`${_prefix}-upload-progress`} + onClick={() => { + store.msgStore + .cancelMessageAttachmentUploadActive(msg) + .catch(() => { + message.error(t('cancelUploadFailedText')) + }) + }} + > + <Progress + type="circle" + status="exception" + percent={uploadProgress || 0} + width={40} + strokeColor="#899095" + trailColor="rgba(0,0,0,0.5)" + /> + </div> </div> - </div> + ) : null} </div> ) } - const renderImage = (msg: IMMessage, isReplyMsg: boolean) => { - // @ts-ignore - const { uploadProgress, uploadFileInfo } = msg + const renderImage = (msg: V2NIMMessageForUI, isReplyMsg: boolean) => { + const { uploadProgress, sendingState } = msg + const attachment = msg.attachment as V2NIMMessageImageAttachment - // uploadProgress 属性只会在上传中存在,uploadFileInfo 在上传中和上传失败时都存在 - if (uploadProgress !== void 0 || uploadFileInfo) { + // 上传中或没有真实 url 时走这里 + if ( + (uploadProgress !== void 0 && uploadProgress < 100) || + !attachment.url + ) { return renderUploadMsg(msg) } @@ -226,50 +271,73 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( ) } - const renderFile = (msg: IMMessage) => { + const renderFile = (msg: V2NIMMessageForUI) => { + let downloadHref = '' + try { + downloadHref = addUrlSearch( + (msg.attachment as V2NIMMessageFileAttachment)?.url, + `download=${(msg.attachment as V2NIMMessageFileAttachment)?.name}` + ) + } catch (error) { + // + } + return ( <div className={`${_prefix}-file-box`}> <CommonIcon className={`${_prefix}-file-icon`} type={ - fileIconMap[getFileType(msg?.attach?.ext)] || 'icon-weizhiwenjian' + fileIconMap[ + getFileType((msg.attachment as V2NIMMessageFileAttachment)?.ext) + ] || 'icon-weizhiwenjian' } /> <div className={`${_prefix}-file-info`}> - <a - download={msg?.attach?.name} - href={addUrlSearch( - msg?.attach?.url, - `download=${msg?.attach?.name}` + {downloadHref ? ( + <a + download={(msg.attachment as V2NIMMessageFileAttachment)?.name} + href={downloadHref} + target="_blank" + > + {(msg.attachment as V2NIMMessageFileAttachment)?.name} + </a> + ) : ( + <span> + {(msg.attachment as V2NIMMessageFileAttachment)?.name} + </span> + )} + <span> + {parseFileSize( + (msg.attachment as V2NIMMessageFileAttachment)?.size )} - target="_blank" - > - {msg?.attach?.name} - </a> - <span>{parseFileSize(msg?.attach?.size)}</span> + </span> </div> </div> ) } - const renderNotification = (msg: IMMessage) => { - switch (msg.attach?.type) { - case 'updateTeam': { - const team: Team = msg.attach?.team || {} + const renderNotification = (msg: V2NIMMessageForUI) => { + const attachment = msg.attachment as V2NIMMessageNotificationAttachment + switch (attachment?.type) { + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_UPDATE_TINFO: { + const team: V2NIMTeam = (attachment?.updatedTeamInfo || + {}) as V2NIMTeam const content: string[] = [] - if (team.avatar !== undefined) { + if (team.avatar !== void 0) { content.push(t('updateTeamAvatar')) } - if (team.name !== undefined) { + if (team.name !== void 0) { content.push(`${t('updateTeamName')}“${team.name}”`) } - if (team.intro !== undefined) { + if (team.intro !== void 0) { content.push(t('updateTeamIntro')) } - if (team.inviteMode) { + if (team.inviteMode !== void 0) { content.push( `${t('updateTeamInviteMode')}“${ - team.inviteMode === 'all' + team.inviteMode === + V2NIMConst.V2NIMTeamInviteMode.V2NIM_TEAM_INVITE_MODE_ALL ? t('teamAll') : localOptions.teamManagerVisible ? t('teamOwnerAndManagerText') @@ -277,10 +345,12 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( }”` ) } - if (team.updateTeamMode) { + if (team.updateInfoMode !== void 0) { content.push( `${t('updateTeamUpdateTeamMode')}“${ - team.updateTeamMode === 'all' + team.updateInfoMode === + V2NIMConst.V2NIMTeamUpdateInfoMode + .V2NIM_TEAM_UPDATE_INFO_MODE_ALL ? t('teamAll') : localOptions.teamManagerVisible ? t('teamOwnerAndManagerText') @@ -288,21 +358,25 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( }”` ) } - if (team.muteType) { + if (team.chatBannedMode !== void 0) { content.push( `${t('updateTeamMute')}${ - team.muteType === 'none' ? t('closeText') : t('openText') + team.chatBannedMode === + V2NIMConst.V2NIMTeamChatBannedMode + .V2NIM_TEAM_CHAT_BANNED_MODE_UNBAN + ? t('closeText') + : t('openText') }` ) } - if (team.ext) { + if (team.serverExtension !== void 0) { let ext: TAllowAt = {} try { - ext = JSON.parse(team.ext) + ext = JSON.parse(team.serverExtension) } catch (error) { // } - if (ext[ALLOW_AT] !== undefined) { + if (ext[ALLOW_AT] !== void 0) { content.push( `${t('updateAllowAt')}“${ ext[ALLOW_AT] === 'manager' @@ -314,50 +388,41 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( ) } } - const attachUser = (msg.attach?.users as UserNameCard[]).find( - (_) => _.account === msg.from - ) return content.length ? ( <div className={`${_prefix}-noti`}> {store.uiStore.getAppellation({ - account: msg.from, + account: msg.senderId, teamId, - nickFromMsg: attachUser?.nick, })}{' '} {content.join('、')} </div> ) : null } - // 有人主动加入群聊 - case 'passTeamApply': + // 申请加入群聊成功 + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_APPLY_PASS: // 邀请加入群聊对方同意 - case 'acceptTeamInvite': { - const attachUser = (msg.attach?.users as UserNameCard[]).find( - (_) => _.account === msg.from - ) + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_INVITE_ACCEPT: { return ( <div className={`${_prefix}-noti`}> {store.uiStore.getAppellation({ - account: msg.from, + account: msg.senderId, teamId, - nickFromMsg: attachUser?.nick, })}{' '} {t('joinTeamText')} </div> ) } // 邀请加入群聊无需验证 - case 'addTeamMembers': { - const accounts: string[] = msg.attach?.accounts || [] + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_INVITE: { + const accounts: string[] = attachment?.targetIds || [] const nicks = accounts .map((item) => { - const attachUser = (msg.attach?.users as UserNameCard[]).find( - (_) => _.account === item - ) return store.uiStore.getAppellation({ account: item, teamId, - nickFromMsg: attachUser?.nick, }) }) .filter((item) => !!item) @@ -369,17 +434,14 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( ) } // 踢出群聊 - case 'removeTeamMembers': { - const accounts: string[] = msg.attach?.accounts || [] + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_KICK: { + const accounts: string[] = attachment?.targetIds || [] const nicks = accounts .map((item) => { - const attachUser = (msg.attach?.users as UserNameCard[]).find( - (_) => _.account === item - ) return store.uiStore.getAppellation({ account: item, teamId, - nickFromMsg: attachUser?.nick, }) }) .filter((item) => !!item) @@ -391,17 +453,14 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( ) } // 增加群管理员 - case 'addTeamManagers': { - const accounts: string[] = msg.attach?.accounts || [] + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_ADD_MANAGER: { + const accounts: string[] = attachment?.targetIds || [] const nicks = accounts .map((item) => { - const attachUser = (msg.attach?.users as UserNameCard[]).find( - (_) => _.account === item - ) return store.uiStore.getAppellation({ account: item, teamId, - nickFromMsg: attachUser?.nick, }) }) .filter((item) => !!item) @@ -413,17 +472,14 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( ) } // 移除群管理员 - case 'removeTeamManagers': { - const accounts: string[] = msg.attach?.accounts || [] + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_REMOVE_MANAGER: { + const accounts: string[] = attachment?.targetIds || [] const nicks = accounts .map((item) => { - const attachUser = (msg.attach?.users as UserNameCard[]).find( - (_) => _.account === item - ) return store.uiStore.getAppellation({ account: item, teamId, - nickFromMsg: attachUser?.nick, }) }) .filter((item) => !!item) @@ -435,33 +491,27 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( ) } // 主动退出群聊 - case 'leaveTeam': { - const attachUser = (msg.attach?.users as UserNameCard[]).find( - (_) => _.account === msg.from - ) + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_LEAVE: { return ( <div className={`${_prefix}-noti`}> {store.uiStore.getAppellation({ - account: msg.from, + account: msg.senderId, teamId, - nickFromMsg: attachUser?.nick, })}{' '} {t('leaveTeamText')} </div> ) } // 转让群主 - case 'transferTeam': { - const attachUser = (msg.attach?.users as UserNameCard[]).find( - (_) => _.account === msg.attach?.account - ) + case V2NIMConst.V2NIMMessageNotificationType + .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_OWNER_TRANSFER: { return ( <div className={`${_prefix}-noti`}> <span className={`${_prefix}-noti-transfer`}> {store.uiStore.getAppellation({ - account: msg.attach?.account, + account: (attachment?.targetIds || [])[0], teamId, - nickFromMsg: attachUser?.nick, })} </span> {t('newGroupOwnerText')} @@ -473,9 +523,9 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( } } - const renderAudio = (msg: IMMessage) => { - const { flow, attach } = msg - const duration = Math.floor(attach?.dur / 1000) || 0 + const renderAudio = (msg: V2NIMMessageForUI) => { + const attachment = msg.attachment as V2NIMMessageAudioAttachment + const duration = Math.floor(attachment?.duration / 1000) || 0 const containerWidth = 80 + 15 * (duration - 1) @@ -487,21 +537,23 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( > <div className={ - flow === 'in' ? `${_prefix}-audio-in` : `${_prefix}-audio-out` + msg.senderId === myAccount + ? `${_prefix}-audio-out` + : `${_prefix}-audio-in` } onClick={() => { pauseAllVideo() const oldAudio = pauseAllAudio() const msgId = oldAudio?.getAttribute('msgId') // 如果是自己,暂停动画 - if (msgId === msg.idClient) { + if (msgId === msg.messageClientId) { animationFlag = false return } - const audio = new Audio(attach?.url) + const audio = new Audio(attachment?.url) // 播放音频,并开始动画 audio.id = 'yx-audio-message' - audio.setAttribute('msgId', msg.idClient) + audio.setAttribute('msgId', msg.messageClientId) audio.play() audioContainerRef.current?.appendChild(audio) audio.addEventListener('ended', () => { @@ -525,25 +577,37 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( ) } - const renderVideo = (msg: IMMessage) => { - // @ts-ignore - const { uploadProgress, uploadFileInfo, attach } = msg + const renderVideo = (msg: V2NIMMessageForUI) => { + const uploadProgress = msg.uploadProgress + const attachment = msg.attachment as V2NIMMessageVideoAttachment + + let url = attachment.url + + if (attachment.file && !url) { + try { + url = URL.createObjectURL(attachment.file) + } catch (error) { + logger.warn('createObjectURL fail: ', attachment) + } + } - // uploadProgress 属性只会在上传中存在,uploadFileInfo 在上传中和上传失败时都存在 - if (uploadProgress !== void 0 || uploadFileInfo) { + // 上传中或没有真实 url 时走这里 + if ((uploadProgress !== void 0 && uploadProgress < 100) || !url) { return renderUploadMsg(msg) } - const url = `${attach?.url}?download=${msg.idClient}.${attach?.ext}` + url = url.startsWith('blob:') + ? url + : `${url}?download=${msg.messageClientId}.${attachment?.ext}` return ( <video src={url} - id={`msg-video-${msg.idClient}`} + id={`msg-video-${msg.messageClientId}`} className={`${_prefix}-video`} controls onPlay={() => { // 播放视频,暂停其他视频和音频 - pauseOtherVideo(msg.idClient) + pauseOtherVideo(msg.messageClientId) pauseAllAudio() }} onError={() => { @@ -553,11 +617,12 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( ) } - const renderLocation = (msg: IMMessage) => { - const { attach, body } = msg - const amapUrl = `https://uri.amap.com/marker?position=${attach?.lng},${attach?.lat}&name=${body}` - const txmapUrl = `https://apis.map.qq.com/uri/v1/marker?marker=coord:${attach?.lat},${attach?.lng};title:${body};addr:${attach?.title}&referer=myapp` - const bdmapUrl = `http://api.map.baidu.com/marker?location=${attach?.lat},${attach?.lng}&title=${body}&content=${attach?.title}&output=html&coord_type=gcj02&src=myapp` + const renderLocation = (msg: V2NIMMessageForUI) => { + const attachment = msg.attachment as V2NIMMessageLocationAttachment + const text = msg.text + const amapUrl = `https://uri.amap.com/marker?position=${attachment?.longitude},${attachment?.latitude}&name=${text}` + const txmapUrl = `https://apis.map.qq.com/uri/v1/marker?marker=coord:${attachment?.latitude},${attachment?.longitude};title:${text};addr:${attachment?.address}&referer=myapp` + const bdmapUrl = `http://api.map.baidu.com/marker?location=${attachment?.latitude},${attachment?.longitude}&title=${text}&content=${attachment?.address}&output=html&coord_type=gcj02&src=myapp` const menu = ( <div className={`${_prefix}-map-menu`}> <div> @@ -587,9 +652,9 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( } > <div className={`${_prefix}-location-card`} ref={locationDomRef}> - <div className={`${_prefix}-location-title`}>{body}</div> + <div className={`${_prefix}-location-title`}>{text}</div> <div className={`${_prefix}-location-subTitle`}> - {attach?.title} + {attachment?.address} </div> <img src="https://yx-web-nosdn.netease.im/common/00685d88b3d4bead5e95479408b5b30f/map.png" @@ -608,7 +673,7 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( content = t('recallReplyMessageText') } const nick = store.uiStore.getAppellation({ - account: replyMsg.from, + account: replyMsg.senderId, teamId, ignoreAlias: true, }) @@ -634,28 +699,28 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( return null } - const renderMsgContent = (msg: IMMessage, isReplyMsg: boolean) => { - switch (msg.type) { - case 'text': - case 'custom': + const renderMsgContent = (msg: V2NIMMessageForUI, isReplyMsg: boolean) => { + switch (msg.messageType) { + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TEXT: + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM: return renderCustomText(msg) - case 'image': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE: return renderImage(msg, isReplyMsg) - case 'file': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_FILE: return renderFile(msg) - case 'notification': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION: return renderNotification(msg) - case 'audio': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO: return renderAudio(msg) - case 'g2': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL: return `[${t('callMsgText')},${notSupportMessageText}]` - case 'geo': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_LOCATION: return renderLocation(msg) - case 'robot': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_ROBOT: return `[${t('robotMsgText')},${notSupportMessageText}]` - case 'tip': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TIPS: return `[${t('tipMsgText')},${notSupportMessageText}]` - case 'video': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_VIDEO: return renderVideo(msg) default: return `[${t('unknowMsgText')},${notSupportMessageText}]` @@ -672,32 +737,32 @@ export const ParseSession: React.FC<IParseSessionProps> = observer( ) export const getMsgContentTipByType = ( - msg: Pick<IMMessage, 'type' | 'body'>, + msg: Pick<V2NIMMessage, 'messageType' | 'text'>, t ): string => { - const { type, body } = msg - switch (type) { - case 'text': - return body || `[${t('textMsgText')}]` - case 'custom': - return body || `[${t('customMsgText')}]` - case 'audio': + const { messageType, text } = msg + switch (messageType) { + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TEXT: + return text || `[${t('textMsgText')}]` + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CUSTOM: + return text || `[${t('customMsgText')}]` + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO: return `[${t('audioMsgText')}]` - case 'file': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_FILE: return `[${t('fileMsgText')}]` - case 'g2': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL: return `[${t('callMsgText')}]` - case 'geo': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_LOCATION: return `[${t('geoMsgText')}]` - case 'image': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_IMAGE: return `[${t('imgMsgText')}]` - case 'notification': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION: return `[${t('notiMsgText')}]` - case 'robot': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_ROBOT: return `[${t('robotMsgText')}]` - case 'tip': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_TIPS: return `[${t('tipMsgText')}]` - case 'video': + case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_VIDEO: return `[${t('videoMsgText')}]` default: return `[${t('unknowMsgText')}]` diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/style/index.ts b/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/style/index.ts index 3ec920e..c7ca3f9 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/style/index.ts +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/style/index.ts @@ -1,3 +1,5 @@ import 'antd/lib/image/style' import 'antd/lib/progress/style' +import 'antd/lib/popover/style' +import 'antd/lib/message/style' import './index.less' diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/ComplexAvatar/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/ComplexAvatar/Container.tsx index d9da732..777d4f7 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/ComplexAvatar/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/ComplexAvatar/Container.tsx @@ -4,6 +4,8 @@ import { message, Modal } from 'antd' import { useStateContext } from '../../hooks/useStateContext' import { useTranslation } from '../../hooks/useTranslation' import { observer } from 'mobx-react' +import { Gender } from '../UserCard' +import { V2NIMConst } from 'nim-web-sdk-ng' export type ComplexAvatarContainerProps = Pick< ComplexAvatarProps, @@ -42,7 +44,7 @@ export const ComplexAvatarContainer: FC<ComplexAvatarContainerProps> = observer( const { t } = useTranslation() const [visible, setVisible] = useState(false) - // const [relation, setRelation] = useState<Relation>('stranger') + const { relation, isInBlacklist } = store.uiStore.getRelation(account) const userInfo = store.uiStore.getFriendWithUserNameCard(account) @@ -66,14 +68,20 @@ export const ComplexAvatarContainer: FC<ComplexAvatarContainerProps> = observer( const handleOnAddFriendClick = async () => { try { if (localOptions.addFriendNeedVerify) { - await store.friendStore.applyFriendActive(account) + await store.friendStore.addFriendActive(account, { + addMode: V2NIMConst.V2NIMFriendAddMode.V2NIM_FRIEND_MODE_TYPE_APPLY, + postscript: '', + }) message.success(t('applyFriendSuccessText')) } else { - await store.friendStore.addFriendActive(account) + await store.friendStore.addFriendActive(account, { + addMode: V2NIMConst.V2NIMFriendAddMode.V2NIM_FRIEND_MODE_TYPE_ADD, + postscript: '', + }) message.success(t('addFriendSuccessText')) } // 发送申请或添加好友成功后解除黑名单 - await store.relationStore.setBlackActive({ account, isAdd: false }) + await store.relationStore.removeUserFromBlockListActive(account) setVisible(false) afterAddFriend?.(account) } catch (error) { @@ -106,10 +114,7 @@ export const ComplexAvatarContainer: FC<ComplexAvatarContainerProps> = observer( const handleOnBlockFriendClick = async () => { try { - await store.relationStore.setBlackActive({ - account, - isAdd: true, - }) + await store.relationStore.addUserToBlockListActive(account) message.success(t('blackSuccessText')) setVisible(false) afterBlockFriend?.(account) @@ -120,10 +125,7 @@ export const ComplexAvatarContainer: FC<ComplexAvatarContainerProps> = observer( const handleOnRemoveBlockFriendClick = async () => { try { - await store.relationStore.setBlackActive({ - account, - isAdd: false, - }) + await store.relationStore.removeUserFromBlockListActive(account) message.success(t('removeBlackSuccessText')) setVisible(false) afterRemoveBlockFriend?.(account) @@ -134,7 +136,10 @@ export const ComplexAvatarContainer: FC<ComplexAvatarContainerProps> = observer( const handleOnSendMsgClick = async () => { setVisible(false) - await store.sessionStore.insertSessionActive('p2p', account) + await store.conversationStore.insertConversationActive( + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P, + account + ) afterSendMsgClick?.() } @@ -144,8 +149,10 @@ export const ComplexAvatarContainer: FC<ComplexAvatarContainerProps> = observer( const handleChangeAlias = async (alias: string) => { try { - if (userInfo.account) { - await store.friendStore.updateFriendActive(userInfo.account, alias) + if (userInfo.accountId) { + await store.friendStore.setFriendInfoActive(userInfo.accountId, { + alias, + }) message.success(t('updateAliasSuccessText')) } } catch (error) { @@ -171,6 +178,13 @@ export const ComplexAvatarContainer: FC<ComplexAvatarContainerProps> = observer( dot={dot} size={size} icon={icon} + account={userInfo.accountId} + gender={userInfo.gender as Gender} + nick={userInfo.name} + tel={userInfo.mobile} + signature={userInfo.sign} + birth={userInfo.birthday} + ext={userInfo.serverExtension} {...userInfo} /> ) diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/CrudeAvatar/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/CrudeAvatar/index.tsx index a673df4..e873b00 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/CrudeAvatar/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/CrudeAvatar/index.tsx @@ -1,16 +1,27 @@ import React, { FC, useEffect, useMemo, useState } from 'react' import { Avatar, Badge } from 'antd' -import { UserNameCard } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/UserServiceInterface' import { Storage } from '@xkit-yx/utils' +import { urls } from '../GroupAvatarSelect' -export interface CrudeAvatarProps - extends Pick<UserNameCard, 'account' | 'avatar' | 'nick'> { +export interface CrudeAvatarProps { + account: string + avatar?: string + nick?: string size?: number icon?: React.ReactNode count?: number dot?: boolean } +// 原生端的群组头像,在 web 显示不了,需要做一下映射。后面等移动端上线后统一用下面的。 +const appUrlMap = { + 'https://s.netease.im/safe/ABg8YjWQWvcqO6sAAAAAAAAAAAA?_im_url=1': urls[0], + 'https://s.netease.im/safe/ABg8YjmQWvcqO6sAAAAAAAABAAA?_im_url=1': urls[1], + 'https://s.netease.im/safe/ABg8YjyQWvcqO6sAAAAAAAABAAA?_im_url=1': urls[2], + 'https://s.netease.im/safe/ABg8YkCQWvcqO6sAAAAAAAABAAA?_im_url=1': urls[3], + 'https://s.netease.im/safe/ABg8YkSQWvcqO6sAAAAAAAABAAA?_im_url=1': urls[4], +} + export const CrudeAvatar: FC<CrudeAvatarProps> = ({ nick, account, @@ -22,6 +33,7 @@ export const CrudeAvatar: FC<CrudeAvatarProps> = ({ }) => { const [imgFailed, setImgFailed] = useState(false) const [bgColor, setBgColor] = useState('') + const [webAvatar, setWebAvatar] = useState<string | void>() const text = useMemo(() => { // 头像不用随备注而改变,产品需求 @@ -49,13 +61,16 @@ export const CrudeAvatar: FC<CrudeAvatarProps> = ({ }, [account]) useEffect(() => { + let webUrl = '' if (avatar) { setImgFailed(false) + webUrl = appUrlMap[avatar] } + setWebAvatar(webUrl ? webUrl : avatar) }, [avatar]) const avatarStyle = useMemo(() => { - if (avatar && !imgFailed) { + if (webAvatar && !imgFailed) { return { verticalAlign: 'middle', } @@ -64,7 +79,7 @@ export const CrudeAvatar: FC<CrudeAvatarProps> = ({ backgroundColor: bgColor, verticalAlign: 'middle', } - }, [avatar, imgFailed, bgColor]) + }, [webAvatar, imgFailed, bgColor]) return ( <Badge @@ -77,7 +92,7 @@ export const CrudeAvatar: FC<CrudeAvatarProps> = ({ <Avatar style={avatarStyle} alt={text} - src={avatar} + src={webAvatar} size={size} icon={icon} onError={() => { diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/Container.tsx index ab88a9c..21f2684 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/Container.tsx @@ -1,4 +1,4 @@ -import React, { FC, useEffect, useState } from 'react' +import React, { FC } from 'react' import { FriendSelectUI, FriendSelectUIProps } from './FriendSelectUI' import { useStateContext } from '../../hooks/useStateContext' import { observer } from 'mobx-react' @@ -9,26 +9,10 @@ export const FriendSelectContainer: FC<FriendSelectContainerProps> = observer( (props) => { const { store } = useStateContext() - // const [loading, setLoading] = useState(false) + const friendsWithoutBlacklist = store.uiStore.friends + .filter((item) => !store.relationStore.blacklist.includes(item.accountId)) + .map((item) => item.accountId) - // useEffect(() => { - // setLoading(true) - // store.uiStore - // .getFriendsWithoutBlacklist() - // .then(() => { - // setLoading(false) - // }) - // .catch(() => { - // setLoading(false) - // }) - // }, [store.uiStore]) - - return ( - <FriendSelectUI - list={store.uiStore.friendsWithoutBlacklist} - // loading={loading} - {...props} - /> - ) + return <FriendSelectUI accounts={friendsWithoutBlacklist} {...props} /> } ) diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectItem.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectItem.tsx index 1d9b636..ca0aa88 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectItem.tsx @@ -1,13 +1,14 @@ import React, { FC } from 'react' import { Checkbox } from 'antd' -import { CrudeAvatar } from '../CrudeAvatar' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' +import { ComplexAvatarContainer } from '../ComplexAvatar' -export interface FriendSelectItemProps extends NimKitCoreTypes.IFriendInfo { +export interface FriendSelectItemProps { isSelected?: boolean onSelect?: (account: string, selected: boolean) => void - canSelect: boolean + canSelect?: boolean prefix?: string + account: string + appellation: string } export const FriendSelectItem: FC<FriendSelectItemProps> = ({ @@ -15,7 +16,8 @@ export const FriendSelectItem: FC<FriendSelectItemProps> = ({ onSelect, canSelect = true, prefix = 'common', - ...props + account, + appellation, }) => { const _prefix = `${prefix}-friend-select-item` @@ -25,15 +27,13 @@ export const FriendSelectItem: FC<FriendSelectItemProps> = ({ <Checkbox checked={isSelected} onChange={(e) => { - onSelect?.(props.account, e.target.checked) + onSelect?.(account, e.target.checked) }} className={`${_prefix}-checkbox`} /> ) : null} - <CrudeAvatar size={32} {...props} /> - <span className={`${_prefix}-label`}> - {props.alias || props.nick || props.account || ''} - </span> + <ComplexAvatarContainer size={32} account={account} canClick={false} /> + <span className={`${_prefix}-label`}>{appellation}</span> </div> ) } diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx index 8b20729..346ec14 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx @@ -1,14 +1,14 @@ import React, { FC, useMemo } from 'react' import { Divider, message, Spin } from 'antd' import { FriendSelectItem } from './FriendSelectItem' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' import { groupByPy } from '../../../utils' import { useTranslation } from '../../hooks/useTranslation' +import { useStateContext } from '../../hooks/useStateContext' export interface FriendSelectUIProps { - list: NimKitCoreTypes.IFriendInfo[] + accounts?: string[] selectedAccounts: string[] - onSelect: (selected: NimKitCoreTypes.IFriendInfo[]) => void + onSelect: (accounts: string[]) => void loading?: boolean max?: number @@ -16,7 +16,7 @@ export interface FriendSelectUIProps { } export const FriendSelectUI: FC<FriendSelectUIProps> = ({ - list, + accounts = [], selectedAccounts, onSelect, loading = false, @@ -26,18 +26,21 @@ export const FriendSelectUI: FC<FriendSelectUIProps> = ({ const _prefix = `${prefix}-friend-select` const { t } = useTranslation() + const { store } = useStateContext() const dataSource = useMemo(() => { - return groupByPy<NimKitCoreTypes.IFriendInfo>( - list, + const _data = accounts.map((account) => ({ + account, + appellation: store.uiStore.getAppellation({ account }), + })) + return groupByPy( + _data, { - firstKey: 'alias', - secondKey: 'nick', - thirdKey: 'account', + firstKey: 'appellation', }, false ) - }, [list]) + }, [accounts, store.uiStore]) const handleSelect = (account: string, selected: boolean) => { let _selectedAccounts: string[] = [] @@ -50,21 +53,19 @@ export const FriendSelectUI: FC<FriendSelectUIProps> = ({ } else if (!selected && selectedAccounts.includes(account)) { _selectedAccounts = selectedAccounts.filter((item) => item !== account) } - const _selectedList = list.filter((item) => - _selectedAccounts.includes(item.account) + const _selectedList = accounts.filter((item) => + _selectedAccounts.includes(item) ) onSelect(_selectedList) } const selectedList = useMemo(() => { - return list.filter((item) => selectedAccounts.includes(item.account)) - }, [list, selectedAccounts]) + return accounts.filter((item) => selectedAccounts.includes(item)) + }, [accounts, selectedAccounts]) const strangerList = useMemo(() => { - return selectedAccounts.filter((item) => - list.every((j) => j.account !== item) - ) - }, [list, selectedAccounts]) + return selectedAccounts.filter((item) => accounts.every((j) => j !== item)) + }, [accounts, selectedAccounts]) return ( <div className={`${_prefix}-wrapper`}> @@ -104,10 +105,11 @@ export const FriendSelectUI: FC<FriendSelectUIProps> = ({ <div className={`${_prefix}-selected-content`}> {selectedList.map((item) => ( <FriendSelectItem - key={`select_${item.account}`} + key={`select_${item}`} canSelect={false} prefix={prefix} - {...item} + account={item} + appellation={store.uiStore.getAppellation({ account: item })} /> ))} </div> diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/MyAvatar/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/MyAvatar/Container.tsx index 099a613..37c5a4d 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/MyAvatar/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/MyAvatar/Container.tsx @@ -5,9 +5,10 @@ import { } from '../ComplexAvatar/ComplexAvatarUI' import { message } from 'antd' import { useStateContext } from '../../hooks/useStateContext' -import { UserNameCard } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/UserServiceInterface' import { useTranslation } from '../../hooks/useTranslation' import { observer } from 'mobx-react' +import { V2NIMUserUpdateParams } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMUserService' +import { Gender } from '../UserCard' export type MyAvatarContainerProps = Pick< ComplexAvatarProps, @@ -16,7 +17,7 @@ export type MyAvatarContainerProps = Pick< canClick?: boolean onCancel?: () => void - afterSave?: (res: UserNameCard) => void + afterSave?: (res: V2NIMUserUpdateParams) => void prefix?: string } @@ -40,22 +41,43 @@ export const MyAvatarContainer: FC<MyAvatarContainerProps> = observer( const userInfo = store.userStore.myUserInfo - const handleSave = ( - params: Pick< - UserNameCard, - 'email' | 'gender' | 'nick' | 'tel' | 'signature' - > & { avatarFile?: File } - ) => { + const handleSave = ({ + avatarFile, + gender, + email, + nick, + tel, + signature, + }: { + avatarFile?: File + gender?: Gender + email?: string + nick?: string + tel?: string + signature?: string + }) => { + const params: V2NIMUserUpdateParams = {} + if (gender !== void 0) { + params.gender = gender + } + if (email !== void 0) { + params.email = email + } + if (nick !== void 0) { + params.name = nick + } + if (tel !== void 0) { + params.mobile = tel + } + if (signature !== void 0) { + params.sign = signature + } store.userStore - .saveMyUserInfoActive({ - ...params, - file: params.avatarFile, - type: params.avatarFile ? 'image' : undefined, - }) - .then((res) => { + .updateSelfUserProfileActive(params, avatarFile) + .then(() => { message.success(t('saveSuccessText')) setVisible(false) - afterSave?.(res) + afterSave?.(params) }) .catch(() => { message.error(t('saveFailedText')) @@ -84,6 +106,13 @@ export const MyAvatarContainer: FC<MyAvatarContainerProps> = observer( dot={dot} size={size} icon={icon} + account={userInfo.accountId} + gender={userInfo.gender as Gender} + nick={userInfo.name} + tel={userInfo.mobile} + signature={userInfo.sign} + birth={userInfo.birthday} + ext={userInfo.serverExtension} {...userInfo} /> ) diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/MyUserCard/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/MyUserCard/index.tsx index d8c97d3..0faaa12 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/MyUserCard/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/MyUserCard/index.tsx @@ -1,18 +1,29 @@ import React, { FC, useEffect, useState, useMemo } from 'react' import { Modal, Input, Select, Upload, message, Form } from 'antd' import { CrudeAvatar } from '../CrudeAvatar' -import { UserNameCard } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/UserServiceInterface' import { useTranslation } from '../../hooks/useTranslation' +import { Gender } from '../UserCard' -export interface MyUserCardProps - extends Omit<UserNameCard, 'createTime' | 'updateTime'> { +export interface MyUserCardProps { + account: string + nick?: string + avatar?: string + signature?: string + gender?: Gender + email?: string + birth?: string + tel?: string + ext?: string visible: boolean - onSave?: ( - params: Pick< - UserNameCard, - 'avatar' | 'email' | 'gender' | 'nick' | 'tel' | 'signature' - > & { avatarFile?: File } - ) => void + onSave?: (params: { + avatarFile?: File + avatar?: string + gender?: Gender + email?: string + nick?: string + tel?: string + signature?: string + }) => void onCancel?: () => void prefix?: string } @@ -30,11 +41,11 @@ export const MyUserCard: FC<MyUserCardProps> = ({ const genderOptions = useMemo( () => [ - { label: t('man'), value: 'male' }, - { label: t('woman'), value: 'female' }, - { label: t('unknow'), value: 'unknown' }, + { label: t('man'), value: Gender.male }, + { label: t('woman'), value: Gender.female }, + { label: t('unknow'), value: Gender.unknown }, ], - [] + [t] ) const [nick, setNick] = useState<string | undefined>(undefined) @@ -42,9 +53,7 @@ export const MyUserCard: FC<MyUserCardProps> = ({ const [avatar, setAvatar] = useState<string | undefined>(undefined) // 头像 file 对象,用于上传 const [avatarFile, setAvatarFile] = useState<File | undefined>(undefined) - const [gender, setGender] = useState< - 'unknown' | 'male' | 'female' | undefined - >(undefined) + const [gender, setGender] = useState<Gender | undefined>(undefined) const [tel, setTel] = useState<string | undefined>(undefined) const [email, setEmail] = useState<string | undefined>(undefined) const [signature, setSignature] = useState<string | undefined>(undefined) diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx index 29cb944..177424f 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react' +import React, { useEffect, useState } from 'react' import { Modal, Input, Radio, Button, Checkbox } from 'antd' import { CloseOutlined, SearchOutlined } from '@ant-design/icons' import { useTranslation } from '../../../common' diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/UserCard/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/UserCard/index.tsx index 6a8c4f0..f2778e7 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/UserCard/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/UserCard/index.tsx @@ -7,12 +7,25 @@ import { MoreOutlined, } from '@ant-design/icons' import { CrudeAvatar } from '../CrudeAvatar' -import { UserNameCard } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/UserServiceInterface' import { useTranslation } from '../../hooks/useTranslation' -import { Relation } from '@xkit-yx/im-store' +import { Relation } from '@xkit-yx/im-store-v2' -export interface UserCardProps - extends Omit<UserNameCard, 'createTime' | 'updateTime'> { +export enum Gender { + unknown = 0, + male = 1, + female = 2, +} + +export interface UserCardProps { + account: string + nick?: string + avatar?: string + signature?: string + gender?: Gender + email?: string + birth?: string + tel?: string + ext?: string alias?: string visible: boolean relation: Relation @@ -49,9 +62,9 @@ export const UserCard: FC<UserCardProps> = ({ const genderOptions = useMemo( () => [ - { label: t('man'), value: 'male' }, - { label: t('woman'), value: 'female' }, - { label: t('unknow'), value: 'unknown' }, + { label: t('man'), value: Gender.male }, + { label: t('woman'), value: Gender.female }, + { label: t('unknow'), value: Gender.unknown }, ], [t] ) diff --git a/react/src/YXUIKit/im-kit-ui/src/common/contextManager/Provider.tsx b/react/src/YXUIKit/im-kit-ui/src/common/contextManager/Provider.tsx index 8eb0159..b41106d 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/contextManager/Provider.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/contextManager/Provider.tsx @@ -1,7 +1,3 @@ -import { - NIMInitializeOptions, - NIMOtherOptions, -} from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/NIMInterface' import React, { FC, ReactNode, @@ -11,46 +7,46 @@ import React, { createContext, memo, } from 'react' -import RootStore from '@xkit-yx/im-store' -import { LocalOptions } from '@xkit-yx/im-store' +import RootStore from '@xkit-yx/im-store-v2' +import { LocalOptions } from '@xkit-yx/im-store-v2/dist/types/types' import { observer } from 'mobx-react' -import { NimKitCoreFactory, NimKitCoreTypes } from '@xkit-yx/core-kit' import { useStateContext } from '../hooks/useStateContext' +import V2NIM from 'nim-web-sdk-ng' import zh from '../locales/zh' +import sdkPkg from 'nim-web-sdk-ng/package.json' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface ContextProps { - nim?: NimKitCoreTypes.INimKitCore + nim?: V2NIM store?: RootStore - initOptions?: NIMInitializeOptions localOptions?: Partial<LocalOptions> t?: (str: keyof typeof zh) => string } export interface ProviderProps { children: ReactNode - sdkVersion?: 1 | 2 - initOptions: NIMInitializeOptions - otherOptions?: NIMOtherOptions localOptions?: Partial<LocalOptions> - funcOptions?: { [key: string]: (...args: any) => void } - nimKitCore?: NimKitCoreTypes.INimKitCore + nim: V2NIM + // 单例模式,用于 vue 带 UI 组件 + singleton?: boolean locale?: 'zh' | 'en' localeConfig?: { [key in keyof typeof zh]?: string } - renderImIdle?: () => JSX.Element renderImDisConnected?: () => JSX.Element renderImConnecting?: () => JSX.Element - singleton?: boolean } export const Context = createContext<ContextProps>({}) const defaultLocalOptions: Required<LocalOptions> = { addFriendNeedVerify: true, - teamBeInviteMode: 'needVerify', - teamJoinMode: 'noVerify', - teamInviteMode: 'manager', - teamUpdateTeamMode: 'manager', - teamUpdateExtMode: 'manager', + teamAgreeMode: V2NIMConst.V2NIMTeamAgreeMode.V2NIM_TEAM_AGREE_MODE_NO_AUTH, + teamJoinMode: V2NIMConst.V2NIMTeamJoinMode.V2NIM_TEAM_JOIN_MODE_FREE, + teamInviteMode: V2NIMConst.V2NIMTeamInviteMode.V2NIM_TEAM_INVITE_MODE_MANAGER, + teamUpdateTeamMode: + V2NIMConst.V2NIMTeamUpdateInfoMode.V2NIM_TEAM_UPDATE_INFO_MODE_MANAGER, + teamUpdateExtMode: + V2NIMConst.V2NIMTeamUpdateExtensionMode + .V2NIM_TEAM_UPDATE_EXTENSION_MODE_MANAGER, leaveOnTransfer: false, needMention: true, p2pMsgReceiptVisible: false, @@ -65,43 +61,14 @@ const defaultLocalOptions: Required<LocalOptions> = { export const Provider: FC<ProviderProps> = memo( ({ children, - initOptions, - otherOptions, - funcOptions, localOptions = defaultLocalOptions, - nimKitCore, - sdkVersion = 1, + nim, locale = 'zh', localeConfig = zh, - renderImIdle, renderImDisConnected, renderImConnecting, singleton = false, }) => { - // 对象参数的引用很容易变,会导致 nim 重新生成,因此最好将对象参数用 useMemo 包裹一下再传进来 - const nim = useMemo(() => { - let _nim: NimKitCoreTypes.INimKitCore - if (nimKitCore) { - _nim = nimKitCore - } else { - const NIM = NimKitCoreFactory(sdkVersion) - if (singleton) { - _nim = NIM.getInstance({ initOptions, otherOptions, funcOptions }) - } else { - _nim = new NIM({ initOptions, otherOptions, funcOptions }) - } - } - _nim.connect() - return _nim - }, [ - initOptions, - otherOptions, - funcOptions, - sdkVersion, - nimKitCore, - singleton, - ]) - const localeMap = useMemo( () => ({ zh, @@ -134,8 +101,8 @@ export const Provider: FC<ProviderProps> = memo( window.__xkit_store__ = { nim, store: rootStore, - initOptions, localOptions: finalLocalOptions, + sdkVersion: sdkPkg.version, } useEffect(() => { @@ -146,25 +113,16 @@ export const Provider: FC<ProviderProps> = memo( } }, [rootStore, singleton]) - useEffect(() => { - return () => { - if (!singleton) { - nim.destroy() - } - } - }, [nim, singleton]) return ( <Context.Provider value={{ store: rootStore, nim, - initOptions, localOptions: finalLocalOptions, t, }} > <App - renderImIdle={renderImIdle} renderImConnecting={renderImConnecting} renderImDisConnected={renderImDisConnected} > @@ -176,36 +134,31 @@ export const Provider: FC<ProviderProps> = memo( ) export const App: FC<{ - renderImIdle?: () => JSX.Element renderImDisConnected?: () => JSX.Element renderImConnecting?: () => JSX.Element -}> = observer( - ({ renderImIdle, renderImConnecting, renderImDisConnected, children }) => { - const { store } = useStateContext() +}> = observer(({ renderImConnecting, renderImDisConnected, children }) => { + const { store } = useStateContext() - const render = () => { - switch (store.connectStore.connectState) { - case 'connected': - return children - case 'idle': - return renderImIdle ? renderImIdle() : null - case 'connecting': - return renderImConnecting ? ( - renderImConnecting() - ) : ( - <span>Loading……</span> - ) - case 'disconnected': - return renderImDisConnected ? ( - renderImDisConnected() - ) : ( - <span>当前网络不可用,请检查网络设置,刷新页面</span> - ) - default: - return null - } + const render = () => { + switch (store.connectStore.loginStatus) { + case V2NIMConst.V2NIMLoginStatus.V2NIM_LOGIN_STATUS_LOGINED: + return children + case V2NIMConst.V2NIMLoginStatus.V2NIM_LOGIN_STATUS_LOGINING: + return renderImConnecting ? ( + renderImConnecting() + ) : ( + <span>Loading……</span> + ) + case V2NIMConst.V2NIMLoginStatus.V2NIM_LOGIN_STATUS_LOGOUT: + return renderImDisConnected ? ( + renderImDisConnected() + ) : ( + <span>当前网络不可用,请检查网络设置,刷新页面</span> + ) + default: + return null } - - return <>{render()}</> } -) + + return <>{render()}</> +}) diff --git a/react/src/YXUIKit/im-kit-ui/src/common/hooks/useStateContext.ts b/react/src/YXUIKit/im-kit-ui/src/common/hooks/useStateContext.ts index 87f6955..9faf369 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/hooks/useStateContext.ts +++ b/react/src/YXUIKit/im-kit-ui/src/common/hooks/useStateContext.ts @@ -2,14 +2,13 @@ import { useContext } from 'react' import { Context, ContextProps } from '../contextManager/Provider' export const useStateContext = (): Required< - Pick<ContextProps, 'nim' | 'store' | 'initOptions' | 'localOptions'> + Pick<ContextProps, 'nim' | 'store' | 'localOptions'> > => { - const { store, nim, initOptions, localOptions } = - useContext<ContextProps>(Context) + const { store, nim, localOptions } = useContext<ContextProps>(Context) - if (!nim || !store || !initOptions || !localOptions) { + if (!nim || !store || !localOptions) { throw new Error('Please use Provider to wrap UI Kit.') } - return { nim, store, initOptions, localOptions } + return { nim, store, localOptions } } diff --git a/react/src/YXUIKit/im-kit-ui/src/common/locales/zh.ts b/react/src/YXUIKit/im-kit-ui/src/common/locales/zh.ts index ebf7001..899823a 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/locales/zh.ts +++ b/react/src/YXUIKit/im-kit-ui/src/common/locales/zh.ts @@ -67,6 +67,7 @@ const LocaleConfig = { applyFriendText: '添加您为好友', acceptResultText: '已同意', rejectResultText: '已拒绝', + expiredResultText: '已过期', beRejectResultText: '拒绝了好友申请', passResultText: '通过了好友申请', rejectTeamInviteText: '拒绝了群邀请', @@ -98,6 +99,7 @@ const LocaleConfig = { callRejectedText: '已拒绝', callTimeoutText: '已超时', callBusyText: '对方忙', + cancelUploadFailedText: '取消上传失败', // conversation-kit onDismissTeamText: '群已被解散', onRemoveTeamText: '您已被移出群组', @@ -202,6 +204,7 @@ const LocaleConfig = { updateTeamManagerSuccessText: '修改群管理员成功', updateTeamManagerFailText: '修改群管理员失败', userNotInTeam: '成员已不在群中', + teamMemberNotExist: '该群聊已不存在', teamManagerLimitText: '谁可以修改资料', teamInviteModeText: '谁可以邀请新成员', teamAtModeText: '谁可以@所有人', @@ -225,6 +228,7 @@ const LocaleConfig = { sendMsgFailedText: '消息发送失败', getHistoryMsgFailedText: '获取历史消息失败', sendBlackFailedText: '您已被对方拉入黑名单', + sendNotFriendFailedText: '当前非好友关系', recallMessageText: '撤回了一条消息', recallReplyMessageText: '该消息已撤回或删除', reeditText: '重新编辑', diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/black-list/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/black-list/Container.tsx index fe2e57a..23423cd 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/black-list/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/black-list/Container.tsx @@ -3,6 +3,7 @@ import { BlackList } from './components/BlackList' import { useEventTracking, useStateContext } from '../../common' import packageJson from '../../../package.json' import { observer } from 'mobx-react' +import sdkPkg from 'nim-web-sdk-ng/package.json' export interface BlackListContainerProps { /** @@ -40,19 +41,18 @@ export const BlackListContainer: FC<BlackListContainerProps> = observer( prefix = 'contact', commonPrefix = 'common', }) => { - const { store, initOptions, nim } = useStateContext() + const { store, nim } = useStateContext() useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'ContactUIKit', - imVersion: nim.version, + imVersion: sdkPkg.version, }) return ( <BlackList list={store.relationStore.blacklist} - // loading={loading} onItemClick={onItemClick} afterSendMsgClick={afterSendMsgClick} renderBlackListHeader={renderBlackListHeader} diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/contact-info/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/contact-info/Container.tsx index 03c38f9..d26ef1d 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/contact-info/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/contact-info/Container.tsx @@ -5,9 +5,14 @@ import { FriendListContainer } from '../friend-list/Container' import { BlackListContainer } from '../black-list/Container' import { GroupListContainer } from '../group-list/Container' import { MsgListContainer } from '../msg-list/Container' -import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' import packageJson from '../../../package.json' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import sdkPgk from 'nim-web-sdk-ng/package.json' +import { + V2NIMFriendAddApplicationForUI, + V2NIMTeamJoinActionInfoForUI, +} from '@xkit-yx/im-store-v2/dist/types/types' export interface ContactInfoContainerProps { /** @@ -17,27 +22,27 @@ export interface ContactInfoContainerProps { /** 通过入群申请后的事件 */ - afterAcceptApplyTeam?: (options: { teamId: string; from: string }) => void + afterAcceptApplyTeam?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void /** 拒绝入群申请后的事件 */ - afterRejectApplyTeam?: (options: { teamId: string; from: string }) => void + afterRejectApplyTeam?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void /** 通过入群邀请后的事件 */ - afterAcceptTeamInvite?: (options: { teamId: string; from: string }) => void + afterAcceptTeamInvite?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void /** 拒绝入群邀请后的事件 */ - afterRejectTeamInvite?: (options: { teamId: string; from: string }) => void + afterRejectTeamInvite?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void /** 通过好友申请后的事件 */ - afterAcceptApplyFriend?: (account: string) => void + afterAcceptApplyFriend?: (application: V2NIMFriendAddApplicationForUI) => void /** 拒绝好友申请后的事件 */ - afterRejectApplyFriend?: (account: string) => void + afterRejectApplyFriend?: (application: V2NIMFriendAddApplicationForUI) => void /** 好友点击事件 */ @@ -49,7 +54,7 @@ export interface ContactInfoContainerProps { /** 群组点击事件 */ - onGroupItemClick?: (team: Team) => void + onGroupItemClick?: (team: V2NIMTeam) => void /** 自定义渲染好友列表为空时内容 */ @@ -121,13 +126,13 @@ export const ContactInfoContainer: React.FC<ContactInfoContainerProps> = prefix = 'contact', commonPrefix = 'common', }) => { - const { store, nim, initOptions } = useStateContext() + const { store, nim } = useStateContext() useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'ContactUIKit', - imVersion: nim.version, + imVersion: sdkPgk.version, }) return store.uiStore.selectedContactType === 'friendList' ? ( diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/Container.tsx index 1129645..4588919 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/Container.tsx @@ -2,9 +2,10 @@ import React, { FC } from 'react' import { ContactList } from './components/ContactList' import { logger } from '../../utils' import { useEventTracking, useStateContext } from '../../common' -import { ContactType } from '@xkit-yx/im-store' +import { ContactType } from '@xkit-yx/im-store-v2' import packageJson from '../../../package.json' import { observer } from 'mobx-react' +import sdkPkg from 'nim-web-sdk-ng/package.json' export interface ContactListContainerProps { /** @@ -25,20 +26,20 @@ export interface ContactListContainerProps { export const ContactListContainer: FC<ContactListContainerProps> = observer( ({ prefix = 'contact', onItemClick, renderCustomContact }) => { - const { nim, store, initOptions } = useStateContext() + const { nim, store } = useStateContext() useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'ContactUIKit', - imVersion: nim.version, + imVersion: sdkPkg.version, }) const handleItemClick = (contactType: ContactType) => { logger.log('选中通讯录导航:', contactType) store.uiStore.selectContactType(contactType) if (contactType === 'msgList') { - store.sysMsgStore.resetSystemMsgUnread() + store.sysMsgStore.setAllApplyMsgRead() } onItemClick?.(contactType) } @@ -48,7 +49,7 @@ export const ContactListContainer: FC<ContactListContainerProps> = observer( selectedContactType={store.uiStore.selectedContactType} onItemClick={handleItemClick} renderCustomContact={renderCustomContact} - systemMsgUnread={store.sysMsgStore.unreadSysMsgCount} + systemMsgUnread={store.sysMsgStore.getTotalUnreadMsgsCount()} prefix={prefix} /> ) diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/components/ContactItem.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/components/ContactItem.tsx index d336af0..ba86f50 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/components/ContactItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/components/ContactItem.tsx @@ -1,6 +1,6 @@ import React, { FC, ReactNode } from 'react' import { Avatar, Badge } from 'antd' -import { ContactType } from '@xkit-yx/im-store' +import { ContactType } from '@xkit-yx/im-store-v2' export interface ContactItemProps { icon: ReactNode diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/components/ContactList.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/components/ContactList.tsx index b7ca0d4..de328b3 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/components/ContactList.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/components/ContactList.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react' import { UserOutlined, TeamOutlined } from '@ant-design/icons' import { ContactItemProps, ContactItem } from './ContactItem' import { useTranslation } from '../../../common' -import { ContactType } from '@xkit-yx/im-store' +import { ContactType } from '@xkit-yx/im-store-v2' export interface ContactListProps { selectedContactType: ContactType | '' diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/Container.tsx index c883d20..ee712c6 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/Container.tsx @@ -1,8 +1,9 @@ -import React, { FC, useEffect } from 'react' +import React, { FC } from 'react' import { FriendList } from './components/FriendList' import { useEventTracking, useStateContext } from '../../common' import packageJson from '../../../package.json' import { observer } from 'mobx-react' +import sdkPkg from 'nim-web-sdk-ng/package.json' export interface FriendListContainerProps { /** @@ -40,29 +41,22 @@ export const FriendListContainer: FC<FriendListContainerProps> = observer( prefix = 'contact', commonPrefix = 'common', }) => { - const { nim, store, initOptions } = useStateContext() + const { nim, store } = useStateContext() useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'ContactUIKit', - imVersion: nim.version, + imVersion: sdkPkg.version, }) - useEffect(() => { - // 订阅好友列表 - const accounts = store.uiStore.friendsWithoutBlacklist.map( - (item) => item.account - ) - store.eventStore.subscribeLoginStateActive(accounts).catch((err) => { - // 忽略报错 - }) - }, [store.uiStore.friendsWithoutBlacklist, store.eventStore]) + const friendsWithoutBlacklist = store.uiStore.friends + .filter((item) => !store.relationStore.blacklist.includes(item.accountId)) + .map((item) => item.accountId) return ( <FriendList - list={store.uiStore.friendsWithoutBlacklist} - // loading={loading} + accounts={friendsWithoutBlacklist} onItemClick={onItemClick} afterSendMsgClick={afterSendMsgClick} renderFriendListHeader={renderFriendListHeader} diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendItem.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendItem.tsx index b98adcb..b6f0be4 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendItem.tsx @@ -28,7 +28,9 @@ export const FriendItem: FC<FriendItemProps> = observer( const { t } = useTranslation() - const isOnline = store.eventStore.stateMap.get(account) === 'online' + // TODO sdk 暂不支持在线状态 + // const isOnline = store.eventStore.stateMap.get(account) === 'online' + const isOnline = 'online' return ( <div diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendList.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendList.tsx index 82c8dd8..cac6eaf 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendList.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendList.tsx @@ -1,12 +1,10 @@ import React, { FC, useMemo } from 'react' -import { Utils, useTranslation } from '../../../common' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' +import { Utils, useStateContext, useTranslation } from '../../../common' import { FriendItem } from './FriendItem' import { Spin, Empty } from 'antd' -// import { List } from 'react-virtualized' export interface FriendListProps { - list: NimKitCoreTypes.IFriendInfo[] + accounts: string[] loading?: boolean onItemClick?: (account: string) => void afterSendMsgClick?: () => void @@ -17,7 +15,7 @@ export interface FriendListProps { } export const FriendList: FC<FriendListProps> = ({ - list, + accounts, loading = false, onItemClick, afterSendMsgClick, @@ -30,17 +28,23 @@ export const FriendList: FC<FriendListProps> = ({ const { t } = useTranslation() + const { store } = useStateContext() + const dataSource = useMemo(() => { - const group = Utils.groupByPy<NimKitCoreTypes.IFriendInfo>( - list, + const data = accounts.map((account) => ({ + account, + appellation: store.uiStore.getAppellation({ account }), + })) + + const group = Utils.groupByPy( + data, { - firstKey: 'alias', - secondKey: 'nick', - thirdKey: 'account', + firstKey: 'appellation', }, false ) - const res: (NimKitCoreTypes.IFriendInfo | string)[] = [] + + const res: ({ account: string; appellation: string } | string)[] = [] group.forEach((item) => { if (!res.includes(item.key)) { res.push(item.key) @@ -48,36 +52,7 @@ export const FriendList: FC<FriendListProps> = ({ res.push(...item.data) }) return res - }, [list]) - - // const rowHeight = (index: number) => { - // if (typeof dataSource[index] === 'string') { - // return 57 - // } - // return 46 - // } - - // const rowRenderer = (data: any) => { - // const item = dataSource[data.index] - // // console.log('rowRenderer:', key, index, item) - // if (typeof item === 'string') { - // return ( - // <div className={`${_prefix}-subtitle`} key={item}> - // {item} - // </div> - // ) - // } - // return ( - // <FriendItem - // key={item.account} - // account={item.account} - // onItemClick={onItemClick} - // afterSendMsgClick={afterSendMsgClick} - // prefix={prefix} - // commonPrefix={commonPrefix} - // /> - // ) - // } + }, [accounts, store.uiStore]) return ( <div className={`${_prefix}-wrapper`}> @@ -89,21 +64,13 @@ export const FriendList: FC<FriendListProps> = ({ <div className={`${_prefix}-list`}> {loading ? ( <Spin /> - ) : !list.length ? ( + ) : !accounts.length ? ( renderFriendListEmpty ? ( renderFriendListEmpty() ) : ( <Empty style={{ marginTop: 10 }} /> ) ) : ( - // <List - // width={810} - // height={469} - // rowCount={dataSource.length} - // rowHeight={rowHeight} - // rowRenderer={rowRenderer} - // containerStyle={{ position: 'static' }} - // ></List> dataSource.map((item) => { if (typeof item === 'string') { return ( diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/Container.tsx index abe9891..1853105 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/Container.tsx @@ -1,18 +1,17 @@ -import React, { FC, useMemo, useEffect, useCallback, useState } from 'react' -import { Spin } from 'antd' +import React, { FC } from 'react' import { GroupList } from './components/GroupList' import { useEventTracking, useStateContext } from '../../common' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' -import { logger } from '../../utils' -import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import sdkPkg from 'nim-web-sdk-ng/package.json' import packageJson from '../../../package.json' import { observer } from 'mobx-react' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface GroupListContainerProps { /** 群组点击事件 */ - onItemClick?: (team: Team) => void + onItemClick?: (team: V2NIMTeam) => void /** 自定义渲染群组列表为空时内容 */ @@ -34,17 +33,20 @@ export const GroupListContainer: FC<GroupListContainerProps> = observer( renderGroupListHeader, prefix = 'contact', }) => { - const { nim, store, initOptions } = useStateContext() + const { nim, store } = useStateContext() useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'ContactUIKit', - imVersion: nim.version, + imVersion: sdkPkg.version, }) - const handleItemClick = async (team: Team) => { - await store.sessionStore.insertSessionActive('team', team.teamId) + const handleItemClick = async (team: V2NIMTeam) => { + await store.conversationStore.insertConversationActive( + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM, + team.teamId + ) onItemClick?.(team) } diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupItem.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupItem.tsx index 4e045da..d83fd62 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupItem.tsx @@ -1,9 +1,9 @@ import React, { FC } from 'react' import { CrudeAvatar } from '../../../common' -import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' -export interface GroupItemProps extends Team { - onItemClick?: (team: Team) => void +export interface GroupItemProps extends V2NIMTeam { + onItemClick?: (team: V2NIMTeam) => void prefix?: string } diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupList.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupList.tsx index 2f50cb1..7850ad5 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupList.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupList.tsx @@ -1,13 +1,13 @@ import React, { FC } from 'react' import { GroupItem } from './GroupItem' -import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' import { useTranslation } from '../../../common' import { Spin, Empty } from 'antd' export interface GroupListProps { - list: Team[] + list: V2NIMTeam[] loading?: boolean - onItemClick?: (team: Team) => void + onItemClick?: (team: V2NIMTeam) => void renderGroupListHeader?: () => JSX.Element renderGroupListEmpty?: () => JSX.Element prefix?: string diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/Container.tsx index 646ecf5..c47954e 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/Container.tsx @@ -5,6 +5,12 @@ import packageJson from '../../../package.json' import { observer } from 'mobx-react' import { message } from 'antd' import { logger } from '../../utils' +import sdkPkg from 'nim-web-sdk-ng/package.json' +import { + V2NIMFriendAddApplicationForUI, + V2NIMTeamJoinActionInfoForUI, +} from '@xkit-yx/im-store-v2/dist/types/types' +import { TMsgItem, TMsgItemType } from './components/MsgItem' export interface MsgListContainerProps { /** @@ -22,27 +28,27 @@ export interface MsgListContainerProps { /** 通过入群申请后的事件 */ - afterAcceptApplyTeam?: (options: { teamId: string; from: string }) => void + afterAcceptApplyTeam?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void /** 拒绝入群申请后的事件 */ - afterRejectApplyTeam?: (options: { teamId: string; from: string }) => void + afterRejectApplyTeam?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void /** 通过入群邀请后的事件 */ - afterAcceptTeamInvite?: (options: { teamId: string; from: string }) => void + afterAcceptTeamInvite?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void /** 拒绝入群邀请后的事件 */ - afterRejectTeamInvite?: (options: { teamId: string; from: string }) => void + afterRejectTeamInvite?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void /** 通过好友申请后的事件 */ - afterAcceptApplyFriend?: (account: string) => void + afterAcceptApplyFriend?: (application: V2NIMFriendAddApplicationForUI) => void /** 拒绝好友申请后的事件 */ - afterRejectApplyFriend?: (account: string) => void + afterRejectApplyFriend?: (application: V2NIMFriendAddApplicationForUI) => void /** 样式前缀 */ @@ -67,31 +73,41 @@ export const MsgListContainer: FC<MsgListContainerProps> = observer( prefix = 'contact', commonPrefix = 'common', }) => { - const { nim, store, initOptions } = useStateContext() + const { nim, store } = useStateContext() const { t } = useTranslation() useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'ContactUIKit', - imVersion: nim.version, + imVersion: sdkPkg.version, }) + const msgs: TMsgItem[] = [ + ...store.sysMsgStore.friendApplyMsgs.map((item) => ({ + ...item, + messageType: TMsgItemType.FRIEND, + })), + ...store.sysMsgStore.teamJoinActionMsgs.map((item) => ({ + ...item, + messageType: TMsgItemType.TEAM, + })), + ].sort((a, b) => b.timestamp - a.timestamp) + const [applyTeamLoaidng, setApplyTeamLoaidng] = useState(false) const [teamInviteLoading, setTeamInviteLoading] = useState(false) const [applyFriendLoading, setApplyFriendLoading] = useState(false) - const onAcceptApplyTeamClick = (options: { - teamId: string - from: string - }) => { + const onAcceptApplyTeamClick = ( + actionInfo: V2NIMTeamJoinActionInfoForUI + ) => { setApplyTeamLoaidng(true) store.teamStore - .passTeamApplyActive(options) + .passTeamApplyActive(actionInfo) .then(() => { message.success(t('acceptedText')) - afterAcceptApplyTeam?.(options) + afterAcceptApplyTeam?.(actionInfo) }) .catch((err) => { message.error(t('acceptFailedText')) @@ -102,16 +118,15 @@ export const MsgListContainer: FC<MsgListContainerProps> = observer( }) } - const onRejectApplyTeamClick = (options: { - teamId: string - from: string - }) => { + const onRejectApplyTeamClick = ( + actionInfo: V2NIMTeamJoinActionInfoForUI + ) => { setApplyTeamLoaidng(true) store.teamStore - .rejectTeamApplyActive(options) + .rejectTeamApplyActive(actionInfo) .then(() => { message.success(t('rejectedText')) - afterRejectApplyTeam?.(options) + afterRejectApplyTeam?.(actionInfo) }) .catch((err) => { message.error(t('rejectFailedText')) @@ -122,16 +137,15 @@ export const MsgListContainer: FC<MsgListContainerProps> = observer( }) } - const onAcceptTeamInviteClick = (options: { - teamId: string - from: string - }) => { + const onAcceptTeamInviteClick = ( + actionInfo: V2NIMTeamJoinActionInfoForUI + ) => { setTeamInviteLoading(true) store.teamStore - .acceptTeamInviteActive(options) + .acceptTeamInviteActive(actionInfo) .then(() => { message.success(t('acceptedText')) - afterAcceptTeamInvite?.(options) + afterAcceptTeamInvite?.(actionInfo) }) .catch((err) => { message.error(t('acceptFailedText')) @@ -142,16 +156,15 @@ export const MsgListContainer: FC<MsgListContainerProps> = observer( }) } - const onRejectTeamInviteClick = (options: { - teamId: string - from: string - }) => { + const onRejectTeamInviteClick = ( + actionInfo: V2NIMTeamJoinActionInfoForUI + ) => { setTeamInviteLoading(true) store.teamStore - .rejectTeamInviteActive(options) + .rejectTeamInviteActive(actionInfo) .then(() => { message.success(t('rejectedText')) - afterRejectTeamInvite?.(options) + afterRejectTeamInvite?.(actionInfo) }) .catch((err) => { message.error(t('rejectFailedText')) @@ -162,12 +175,14 @@ export const MsgListContainer: FC<MsgListContainerProps> = observer( }) } - const onAcceptApplyFriendClick = async (account: string) => { + const onAcceptApplyFriendClick = async ( + application: V2NIMFriendAddApplicationForUI + ) => { try { setApplyFriendLoading(true) - await store.friendStore.passFriendApplyActive(account) + await store.friendStore.acceptAddApplicationActive(application) message.success(t('acceptedText')) - afterAcceptApplyFriend?.(account) + afterAcceptApplyFriend?.(application) } catch (error) { message.error(t('acceptFailedText')) logger.error('同意该申请失败: ', error) @@ -176,13 +191,15 @@ export const MsgListContainer: FC<MsgListContainerProps> = observer( } } - const onRejectApplyFriendClick = (account: string) => { + const onRejectApplyFriendClick = ( + application: V2NIMFriendAddApplicationForUI + ) => { setApplyFriendLoading(true) store.friendStore - .rejectFriendApplyActive(account) + .rejectAddApplicationActive(application) .then(() => { message.success(t('rejectedText')) - afterRejectApplyFriend?.(account) + afterRejectApplyFriend?.(application) }) .catch((err) => { message.error(t('rejectFailedText')) @@ -195,7 +212,7 @@ export const MsgListContainer: FC<MsgListContainerProps> = observer( return ( <MsgList - msgs={store.uiStore.applyMsgList} + msgs={msgs} applyTeamLoaidng={applyTeamLoaidng} teamInviteLoading={teamInviteLoading} applyFriendLoading={applyFriendLoading} diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/components/MsgItem.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/components/MsgItem.tsx index a7f7b7b..9500d32 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/components/MsgItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/components/MsgItem.tsx @@ -7,20 +7,40 @@ import { } from '../../../common' import { Button } from 'antd' import { CheckCircleFilled, CloseCircleFilled } from '@ant-design/icons' -import { SystemMessage } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/SystemMessageServiceInterface' import { observer } from 'mobx-react' +import { + V2NIMFriendAddApplicationForUI, + V2NIMTeamJoinActionInfoForUI, +} from '@xkit-yx/im-store-v2/dist/types/types' +import { V2NIMConst } from 'nim-web-sdk-ng' + +export enum TMsgItemType { + FRIEND = 'friend', + TEAM = 'team', +} + +export type TMsgItem = ( + | V2NIMFriendAddApplicationForUI + | V2NIMTeamJoinActionInfoForUI +) & { + messageType: TMsgItemType +} export interface MsgItemProps { - msg: SystemMessage + msg: TMsgItem applyTeamLoaidng?: boolean teamInviteLoading?: boolean applyFriendLoading?: boolean - onAcceptApplyTeamClick?: (options: { teamId: string; from: string }) => void - onRejectApplyTeamClick?: (options: { teamId: string; from: string }) => void - onAcceptTeamInviteClick?: (options: { teamId: string; from: string }) => void - onRejectTeamInviteClick?: (options: { teamId: string; from: string }) => void - onAcceptApplyFriendClick?: (account: string) => void - onRejectApplyFriendClick?: (account: string) => void + onAcceptApplyTeamClick?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void + onRejectApplyTeamClick?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void + onAcceptTeamInviteClick?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void + onRejectTeamInviteClick?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void + onAcceptApplyFriendClick?: ( + application: V2NIMFriendAddApplicationForUI + ) => void + onRejectApplyFriendClick?: ( + application: V2NIMFriendAddApplicationForUI + ) => void afterSendMsgClick?: () => void prefix?: string commonPrefix?: string @@ -50,151 +70,90 @@ export const MsgItem: FC<MsgItemProps> = observer( const { store } = useStateContext() const handleRejectApplyTeamClick = () => { - onRejectApplyTeamClick?.({ - teamId: msg.attach?.toTeam?.teamId, - from: msg.attach?.fromUser?.account, - }) + onRejectApplyTeamClick?.(msg as V2NIMTeamJoinActionInfoForUI) } const handleAcceptApplyTeamClick = () => { - onAcceptApplyTeamClick?.({ - teamId: msg.attach?.toTeam?.teamId, - from: msg.attach?.fromUser?.account, - }) + onAcceptApplyTeamClick?.(msg as V2NIMTeamJoinActionInfoForUI) } const handleRejectTeamInviteClick = () => { - onRejectTeamInviteClick?.({ - teamId: msg.attach?.toTeam?.teamId, - from: msg.attach?.fromUser?.account, - }) + onRejectTeamInviteClick?.(msg as V2NIMTeamJoinActionInfoForUI) } const handleAcceptTeamInviteClick = () => { - onAcceptTeamInviteClick?.({ - teamId: msg.attach?.toTeam?.teamId, - from: msg.attach?.fromUser?.account, - }) + onAcceptTeamInviteClick?.(msg as V2NIMTeamJoinActionInfoForUI) } const handleRejectApplyFriendClick = () => { - onRejectApplyFriendClick?.(msg.attach?.fromUser?.account) + onRejectApplyFriendClick?.(msg as V2NIMFriendAddApplicationForUI) } const handleAcceptApplyFriendClick = () => { - onAcceptApplyFriendClick?.(msg.attach?.fromUser?.account) + onAcceptApplyFriendClick?.(msg as V2NIMFriendAddApplicationForUI) } - const renderMsg = () => { - switch (msg.type) { - case 'applyTeam': - // 某人申请加入群聊 + const renderFriendApplyMsg = () => { + const applyMsg = msg as V2NIMFriendAddApplicationForUI + // 自己是否是申请者 + const isMeApplicant = + applyMsg.applicantAccountId === store.userStore.myUserInfo.accountId + switch (applyMsg.status) { + case V2NIMConst.V2NIMFriendAddApplicationStatus + .V2NIM_FRIEND_ADD_APPLICATION_STATUS_AGREED: return ( <> <div className={`${_prefix}-flex`}> - <CrudeAvatar - account={msg.attach?.toTeam?.teamId} - nick={msg.attach?.toTeam?.name} - avatar={msg.attach?.toTeam?.avatar} + <ComplexAvatarContainer + account={applyMsg.applicantAccountId} + prefix={commonPrefix} + afterSendMsgClick={afterSendMsgClick} /> - <span className={`${_prefix}-label`}> + <span className={`${_prefix}-name`}> {store.uiStore.getAppellation({ - account: msg.attach?.fromUser?.account, - })}{' '} - {t('applyTeamText')} “ - {msg.attach?.toTeam?.name || msg.attach?.toTeam?.teamId}” + account: applyMsg.applicantAccountId, + })} </span> + <span className={`${_prefix}-label`}> + {t('applyFriendText')} + </span> + </div> + <div className={`${_prefix}-state`}> + <CheckCircleFilled className={`${_prefix}-state-icon`} /> + <span>{t('acceptResultText')}</span> </div> - {msg.state === 'pass' ? ( - <div className={`${_prefix}-state`}> - <CheckCircleFilled className={`${_prefix}-state-icon`} /> - <span>{t('acceptResultText')}</span> - </div> - ) : msg.state === 'decline' ? ( - <div className={`${_prefix}-state`}> - <CloseCircleFilled className={`${_prefix}-state-icon`} /> - <span>{t('rejectResultText')}</span> - </div> - ) : ( - <div className={`${_prefix}-flex`}> - <Button - className={`${_prefix}-reject-btn`} - onClick={handleRejectApplyTeamClick} - loading={applyTeamLoaidng} - > - {t('rejectText')} - </Button> - <Button - onClick={handleAcceptApplyTeamClick} - loading={applyTeamLoaidng} - type="primary" - > - {t('acceptText')} - </Button> - </div> - )} </> ) - case 'teamInvite': - // 某人邀请你进群 - return ( + case V2NIMConst.V2NIMFriendAddApplicationStatus + .V2NIM_FRIEND_ADD_APPLICATION_STATUS_REJECTED: + return isMeApplicant ? ( <> <div className={`${_prefix}-flex`}> - <CrudeAvatar - account={msg.attach?.toTeam?.teamId} - nick={msg.attach?.toTeam?.name} - avatar={msg.attach?.toTeam?.avatar} + <ComplexAvatarContainer + account={applyMsg.recipientAccountId} + prefix={commonPrefix} + afterSendMsgClick={afterSendMsgClick} /> - <span className={`${_prefix}-label`}> + <span className={`${_prefix}-name`}> {store.uiStore.getAppellation({ - account: msg.attach?.fromUser?.account, + account: applyMsg.recipientAccountId, })}{' '} - {t('inviteTeamText')} “ - {msg.attach?.toTeam?.name || msg.attach?.toTeam?.teamId}” + {t('beRejectResultText')} </span> </div> - {msg.state === 'pass' ? ( - <div className={`${_prefix}-state`}> - <CheckCircleFilled className={`${_prefix}-state-icon`} /> - <span>{t('acceptResultText')}</span> - </div> - ) : msg.state === 'decline' ? ( - <div className={`${_prefix}-state`}> - <CloseCircleFilled className={`${_prefix}-state-icon`} /> - <span>{t('rejectResultText')}</span> - </div> - ) : ( - <div className={`${_prefix}-flex`}> - <Button - className={`${_prefix}-reject-btn`} - onClick={handleRejectTeamInviteClick} - loading={teamInviteLoading} - > - {t('rejectText')} - </Button> - <Button - onClick={handleAcceptTeamInviteClick} - loading={teamInviteLoading} - type="primary" - > - {t('acceptText')} - </Button> - </div> - )} </> - ) - case 'rejectTeamApply': - // 管理员拒绝你的入群申请 - return ( + ) : ( <> <div className={`${_prefix}-flex`}> - <CrudeAvatar - account={msg.attach?.fromTeam?.teamId} - nick={msg.attach?.fromTeam?.name} - avatar={msg.attach?.fromTeam?.avatar} + <ComplexAvatarContainer + account={applyMsg.applicantAccountId} + prefix={commonPrefix} + afterSendMsgClick={afterSendMsgClick} /> <span className={`${_prefix}-name`}> - {msg.attach?.fromTeam?.name || msg.attach?.fromTeam?.teamId} + {store.uiStore.getAppellation({ + account: applyMsg.applicantAccountId, + })} </span> </div> <div className={`${_prefix}-state`}> @@ -203,126 +162,89 @@ export const MsgItem: FC<MsgItemProps> = observer( </div> </> ) - case 'rejectTeamInvite': - // 对方拒绝你的群邀请 + case V2NIMConst.V2NIMFriendAddApplicationStatus + .V2NIM_FRIEND_ADD_APPLICATION_STATUS_EXPIRED: + return isMeApplicant ? null : ( + <> + <div className={`${_prefix}-flex`}> + <ComplexAvatarContainer + account={applyMsg.applicantAccountId} + prefix={commonPrefix} + afterSendMsgClick={afterSendMsgClick} + /> + <span className={`${_prefix}-name`}> + {store.uiStore.getAppellation({ + account: applyMsg.applicantAccountId, + })} + </span> + <span className={`${_prefix}-label`}> + {t('applyFriendText')} + </span> + </div> + <div className={`${_prefix}-state`}> + <CloseCircleFilled className={`${_prefix}-state-icon`} /> + <span>{t('expiredResultText')}</span> + </div> + </> + ) + case V2NIMConst.V2NIMFriendAddApplicationStatus + .V2NIM_FRIEND_ADD_APPLICATION_STATUS_INIT: return ( <> <div className={`${_prefix}-flex`}> <ComplexAvatarContainer - account={msg.attach?.fromUser?.account} + account={applyMsg.applicantAccountId} prefix={commonPrefix} afterSendMsgClick={afterSendMsgClick} /> <span className={`${_prefix}-name`}> {store.uiStore.getAppellation({ - account: msg.attach?.fromUser?.account, - })}{' '} - {t('rejectTeamInviteText')} + account: applyMsg.applicantAccountId, + })} + </span> + <span className={`${_prefix}-label`}> + {t('applyFriendText')} </span> </div> - {/* <div className={`${_prefix}-state`}> - <CloseCircleFilled className={`${_prefix}-state-icon`} /> - <span>{t('rejectResultText')}</span> - </div> */} + <div className={`${_prefix}-flex`}> + <Button + className={`${_prefix}-reject-btn`} + onClick={handleRejectApplyFriendClick} + loading={applyFriendLoading} + > + {t('rejectText')} + </Button> + <Button + onClick={handleAcceptApplyFriendClick} + loading={applyFriendLoading} + type="primary" + > + {t('acceptText')} + </Button> + </div> </> ) - case 'friendRequest': { - switch (msg.attach?.type) { - case 'applyFriend': - // 某人请求添加你为好友 - return ( - <> - <div className={`${_prefix}-flex`}> - <ComplexAvatarContainer - account={msg.attach?.fromUser?.account} - prefix={commonPrefix} - afterSendMsgClick={afterSendMsgClick} - /> - <span className={`${_prefix}-name`}> - {store.uiStore.getAppellation({ - account: msg.attach?.fromUser?.account, - })} - </span> - <span className={`${_prefix}-label`}> - {t('applyFriendText')} - </span> - </div> - {msg.state === 'pass' ? ( - <div className={`${_prefix}-state`}> - <CheckCircleFilled className={`${_prefix}-state-icon`} /> - <span>{t('acceptResultText')}</span> - </div> - ) : msg.state === 'decline' ? ( - <div className={`${_prefix}-state`}> - <CloseCircleFilled className={`${_prefix}-state-icon`} /> - <span>{t('rejectResultText')}</span> - </div> - ) : ( - <div className={`${_prefix}-flex`}> - <Button - className={`${_prefix}-reject-btn`} - onClick={handleRejectApplyFriendClick} - loading={applyFriendLoading} - > - {t('rejectText')} - </Button> - <Button - onClick={handleAcceptApplyFriendClick} - loading={applyFriendLoading} - type="primary" - > - {t('acceptText')} - </Button> - </div> - )} - </> - ) - case 'rejectFriendApply': - // 对方拒绝了好友申请 - return ( - <> - <div className={`${_prefix}-flex`}> - <ComplexAvatarContainer - account={msg.attach?.fromUser?.account} - prefix={commonPrefix} - afterSendMsgClick={afterSendMsgClick} - /> - <span className={`${_prefix}-name`}> - {store.uiStore.getAppellation({ - account: msg.attach?.fromUser?.account, - })}{' '} - {t('beRejectResultText')} - </span> - </div> - {/* <div className={`${_prefix}-state`}> - <CloseCircleFilled className={`${_prefix}-state-icon`} /> - <span>{t('beRejectResultText')}</span> - </div> */} - </> - ) - case 'passFriendApply': - // 对方同意了好友申请 - return ( - <> - <div className={`${_prefix}-flex`}> - <ComplexAvatarContainer - account={msg.attach?.fromUser?.account} - prefix={commonPrefix} - afterSendMsgClick={afterSendMsgClick} - /> - <span className={`${_prefix}-name`}> - {store.uiStore.getAppellation({ - account: msg.attach?.fromUser?.account, - })}{' '} - {t('passResultText')} - </span> - </div> - </> - ) - default: - return null - } - } + default: + return null + } + } + + const renderTeamJoinActionMsg = () => { + // TODO 暂不支持群相关申请 + const teamJoinActionMsg = msg as V2NIMTeamJoinActionInfoForUI + switch (teamJoinActionMsg.actionType) { + case V2NIMConst.V2NIMTeamJoinActionType + .V2NIM_TEAM_JOIN_ACTION_TYPE_APPLICATION: + return null + case V2NIMConst.V2NIMTeamJoinActionType + .V2NIM_TEAM_JOIN_ACTION_TYPE_INVITATION: + return null + case V2NIMConst.V2NIMTeamJoinActionType + .V2NIM_TEAM_JOIN_ACTION_TYPE_REJECT_APPLICATION: + return null + case V2NIMConst.V2NIMTeamJoinActionType + .V2NIM_TEAM_JOIN_ACTION_TYPE_REJECT_INVITATION: + return null default: return null } @@ -335,7 +257,9 @@ export const MsgItem: FC<MsgItemProps> = observer( e.stopPropagation() }} > - {renderMsg()} + {msg.messageType === TMsgItemType.FRIEND + ? renderFriendApplyMsg() + : renderTeamJoinActionMsg()} </div> ) } diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/components/MsgList.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/components/MsgList.tsx index 2ca89fc..ebe06b2 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/components/MsgList.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/msg-list/components/MsgList.tsx @@ -1,21 +1,28 @@ import React, { FC } from 'react' -import { useTranslation } from '../../../common' -import { MsgItem } from './MsgItem' +import { useStateContext, useTranslation } from '../../../common' +import { MsgItem, TMsgItem, TMsgItemType } from './MsgItem' import { Spin, Empty } from 'antd' -import { SystemMessage } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/SystemMessageServiceInterface' +import { + V2NIMFriendAddApplicationForUI, + V2NIMTeamJoinActionInfoForUI, +} from '@xkit-yx/im-store-v2/dist/types/types' export interface MsgListProps { - msgs: SystemMessage[] + msgs: TMsgItem[] listLoading?: boolean applyTeamLoaidng?: boolean teamInviteLoading?: boolean applyFriendLoading?: boolean - onAcceptApplyTeamClick?: (options: { teamId: string; from: string }) => void - onRejectApplyTeamClick?: (options: { teamId: string; from: string }) => void - onAcceptTeamInviteClick?: (options: { teamId: string; from: string }) => void - onRejectTeamInviteClick?: (options: { teamId: string; from: string }) => void - onAcceptApplyFriendClick?: (account: string) => void - onRejectApplyFriendClick?: (account: string) => void + onAcceptApplyTeamClick?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void + onRejectApplyTeamClick?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void + onAcceptTeamInviteClick?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void + onRejectTeamInviteClick?: (actionInfo: V2NIMTeamJoinActionInfoForUI) => void + onAcceptApplyFriendClick?: ( + application: V2NIMFriendAddApplicationForUI + ) => void + onRejectApplyFriendClick?: ( + application: V2NIMFriendAddApplicationForUI + ) => void afterSendMsgClick?: () => void renderMsgListHeader?: () => JSX.Element renderMsgListEmpty?: () => JSX.Element @@ -43,6 +50,8 @@ export const MsgList: FC<MsgListProps> = ({ }) => { const _prefix = `${prefix}-msg` + const { store } = useStateContext() + const { t } = useTranslation() return ( @@ -62,7 +71,15 @@ export const MsgList: FC<MsgListProps> = ({ <div className={`${_prefix}-content`}> {msgs.map((item) => ( <MsgItem - key={`${item.idServer}_${item.from}_${item.to}_${item.type}`} + key={ + item.messageType === TMsgItemType.FRIEND + ? store.sysMsgStore.createFriendApplyMsgKey( + item as V2NIMFriendAddApplicationForUI + ) + : store.sysMsgStore.createTeamJoinActionMsgKey( + item as V2NIMTeamJoinActionInfoForUI + ) + } msg={item} applyTeamLoaidng={applyTeamLoaidng} teamInviteLoading={teamInviteLoading} 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 8f1cba4..040fee1 100644 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/conversation/Container.tsx @@ -1,12 +1,14 @@ -import React, { FC, useEffect, useState } from 'react' +import React, { FC, useMemo } from 'react' import { ConversationList, ConversationCallbackProps, } from './components/ConversationList' import { useStateContext, useEventTracking } from '../common' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' import packageJson from '../../package.json' import { observer } from 'mobx-react' +import { V2NIMConversation } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +import sdkPkg from 'nim-web-sdk-ng/package.json' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface ConversationContainerProps { /** @@ -20,62 +22,62 @@ export interface ConversationContainerProps { /** 会话点击事件 */ - onSessionItemClick?: (id: string) => void + onConversationItemClick?: (id: string) => void /** 会话删除事件 */ - onSessionItemDeleteClick?: (id: string) => void + onConversationItemDeleteClick?: (id: string) => void /** 会话置顶状态改变事件 */ - onSessionItemStickTopChange?: (id: string, isTop: boolean) => void + onConversationItemStickTopChange?: (id: string, isTop: boolean) => void /** 会话免打扰状态改变事件 */ - onSessionItemMuteChange?: (id: string, mute: boolean) => void + onConversationItemMuteChange?: (id: string, mute: boolean) => void /** 自定义渲染会话列表为空时内容 */ - renderSessionListEmpty?: () => JSX.Element | null | undefined + renderConversationListEmpty?: () => JSX.Element | null | undefined /** 自定义渲染会话类型是单聊的内容 */ - renderCustomP2pSession?: ( + renderCustomP2pConversation?: ( options: { - session: NimKitCoreTypes.ISession + conversation: V2NIMConversation } & ConversationCallbackProps ) => JSX.Element | null | undefined /** 自定义渲染会话类型是群聊的内容 */ - renderCustomTeamSession?: ( + renderCustomTeamConversation?: ( options: { - session: NimKitCoreTypes.ISession - } & Omit<ConversationCallbackProps, 'onSessionItemMuteChange'> + conversation: V2NIMConversation + } & Omit<ConversationCallbackProps, 'onConversationItemMuteChange'> ) => JSX.Element | null | undefined /** - 自定义会话名称。如果 p2p 会话定义了 renderCustomP2pSession 或群组会话定义了 renderCustomTeamSession 则不生效。 + 自定义会话名称。如果 p2p 会话定义了 renderCustomP2pConversation 或群组会话定义了 renderCustomTeamConversation 则不生效。 */ - renderSessionName?: (options: { - session: NimKitCoreTypes.ISession + renderConversationName?: (options: { + conversation: V2NIMConversation }) => JSX.Element | null | undefined /** - 自定义会话消息。如果 p2p 会话定义了 renderCustomP2pSession 或群组会话定义了 renderCustomTeamSession 则不生效。 + 自定义会话消息。如果 p2p 会话定义了 renderCustomP2pConversation 或群组会话定义了 renderCustomTeamConversation 则不生效。 */ - renderSessionMsg?: (options: { - session: NimKitCoreTypes.ISession + renderConversationMsg?: (options: { + conversation: V2NIMConversation }) => JSX.Element | null | undefined /** - 自定义 p2p 会话头像。如果定义了 renderCustomP2pSession 则不生效。 + 自定义 p2p 会话头像。如果定义了 renderCustomP2pConversation 则不生效。 */ - renderP2pSessionAvatar?: (options: { - session: NimKitCoreTypes.ISession + renderP2pConversationAvatar?: (options: { + conversation: V2NIMConversation }) => JSX.Element | null | undefined /** - 自定义群组会话头像。如果定义了 renderCustomTeamSession 则不生效。 + 自定义群组会话头像。如果定义了 renderCustomTeamConversation 则不生效。 */ - renderTeamSessionAvatar?: (options: { - session: NimKitCoreTypes.ISession + renderTeamConversationAvatar?: (options: { + conversation: V2NIMConversation }) => JSX.Element | null | undefined } @@ -83,100 +85,90 @@ export const ConversationContainer: FC<ConversationContainerProps> = observer( ({ prefix = 'conversation', commonPrefix = 'common', - onSessionItemClick, - onSessionItemDeleteClick, - onSessionItemStickTopChange, - onSessionItemMuteChange, - renderSessionListEmpty, - renderCustomP2pSession, - renderCustomTeamSession, - renderP2pSessionAvatar, - renderTeamSessionAvatar, - renderSessionName, - renderSessionMsg, + onConversationItemClick, + onConversationItemDeleteClick, + onConversationItemStickTopChange, + onConversationItemMuteChange, + renderConversationListEmpty, + renderCustomP2pConversation, + renderCustomTeamConversation, + renderP2pConversationAvatar, + renderTeamConversationAvatar, + renderConversationName, + renderConversationMsg, }) => { - const { nim, store, initOptions, localOptions } = useStateContext() - const sessionId = store.uiStore.selectedSession + const { nim, store } = useStateContext() useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'ConversationUIKit', - imVersion: nim.version, + imVersion: sdkPkg.version, }) - // 处理 team 会话列表 @ 提醒 - // const [sessionList, setSessionList] = useState<NimKitCoreTypes.ISession[]>( - // [] - // ) - - const handleSessionItemClick = async ( - session: NimKitCoreTypes.ISession + const handleConversationItemClick = async ( + conversation: V2NIMConversation ) => { - await store.uiStore.selectSession(session.id) - onSessionItemClick?.(session.id) + await store.uiStore.selectConversation(conversation.conversationId) + onConversationItemClick?.(conversation.conversationId) } - const handleSessionItemDeleteClick = async ( - session: NimKitCoreTypes.ISession + const handleConversationItemDeleteClick = async ( + conversation: V2NIMConversation ) => { - await store.sessionStore.deleteSessionActive(session.id) - onSessionItemDeleteClick?.(session.id) + await store.conversationStore.deleteConversationActive( + conversation.conversationId + ) + onConversationItemDeleteClick?.(conversation.conversationId) } - const handleSessionItemStickTopChange = async ( - session: NimKitCoreTypes.ISession, + const handleConversationItemStickTopChange = async ( + conversation: V2NIMConversation, isTop: boolean ) => { - if (isTop) { - await store.sessionStore.addStickTopSessionActive(session.id) - } else { - await store.sessionStore.deleteStickTopSessionActive(session.id) - } - onSessionItemStickTopChange?.(session.id, isTop) + await store.conversationStore.stickTopConversationActive( + conversation.conversationId, + isTop + ) + onConversationItemStickTopChange?.(conversation.conversationId, isTop) } - const handleSessionItemMuteChange = async ( - session: NimKitCoreTypes.ISession, + const handleConversationItemMuteChange = async ( + conversation: V2NIMConversation, mute: boolean ) => { - await store.relationStore.setMuteActive({ - account: session.to, - isAdd: mute, - }) - onSessionItemMuteChange?.(session.id, mute) + await store.relationStore.setP2PMessageMuteModeActive( + nim.V2NIMConversationIdUtil.parseConversationTargetId( + conversation.conversationId + ), + mute + ? V2NIMConst.V2NIMP2PMessageMuteMode.V2NIM_P2P_MESSAGE_MUTE_MODE_ON + : V2NIMConst.V2NIMP2PMessageMuteMode.V2NIM_P2P_MESSAGE_MUTE_MODE_OFF + ) + onConversationItemMuteChange?.(conversation.conversationId, mute) } - // useEffect(() => { - // setSessionList([...store.uiStore.sessionList]) - // }, [store.uiStore.sessionList]) - - useEffect(() => { - // 订阅会话列表中 p2p 的在线离线状态 - const accounts = store.uiStore.sessionList - .filter((item) => item.scene === 'p2p') - .map((item) => item.to) - store.eventStore.subscribeLoginStateActive(accounts).catch((err) => { - // 忽略报错 - }) - }, [store.uiStore.sessionList, store.eventStore]) + const conversations = useMemo(() => { + return store.uiStore.conversations.sort( + (a, b) => b.sortOrder - a.sortOrder + ) + }, [store.uiStore.conversations]) return ( <ConversationList - sessions={store.uiStore.sessionList} - // loading={loading} - selectedSession={store.uiStore.selectedSession} - onSessionItemClick={handleSessionItemClick} - onSessionItemDeleteClick={handleSessionItemDeleteClick} - onSessionItemStickTopChange={handleSessionItemStickTopChange} - onSessionItemMuteChange={handleSessionItemMuteChange} - renderCustomP2pSession={renderCustomP2pSession} - renderCustomTeamSession={renderCustomTeamSession} - renderSessionListEmpty={renderSessionListEmpty} - renderP2pSessionAvatar={renderP2pSessionAvatar} - renderTeamSessionAvatar={renderTeamSessionAvatar} - renderSessionName={renderSessionName} - renderSessionMsg={renderSessionMsg} + conversations={conversations} + selectedConversation={store.uiStore.selectedConversation} + onConversationItemClick={handleConversationItemClick} + onConversationItemDeleteClick={handleConversationItemDeleteClick} + onConversationItemStickTopChange={handleConversationItemStickTopChange} + onConversationItemMuteChange={handleConversationItemMuteChange} + renderCustomP2pConversation={renderCustomP2pConversation} + renderCustomTeamConversation={renderCustomTeamConversation} + renderConversationListEmpty={renderConversationListEmpty} + renderP2pConversationAvatar={renderP2pConversationAvatar} + renderTeamConversationAvatar={renderTeamConversationAvatar} + renderConversationName={renderConversationName} + renderConversationMsg={renderConversationMsg} prefix={prefix} commonPrefix={commonPrefix} /> diff --git a/react/src/YXUIKit/im-kit-ui/src/conversation/components/ConversationItem.tsx b/react/src/YXUIKit/im-kit-ui/src/conversation/components/ConversationItem.tsx index bc67729..8f8a589 100644 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/components/ConversationItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/conversation/components/ConversationItem.tsx @@ -7,21 +7,22 @@ import { getMsgContentTipByType, useTranslation, } from '../../common' -import { IMMessage } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' +import { V2NIMLastMessage } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface ConversationItemProps { isTop: boolean isMute: boolean - sessionName: string + conversationName: string menuRenderer: ReactElement avatarRenderer: ReactElement time: number - lastMsg: IMMessage | null | undefined + lastMessage?: V2NIMLastMessage isSelected: boolean onItemClick: () => void - renderSessionMsgIsRead?: () => void - sessionNameRenderer?: JSX.Element | null - sessionMsgRenderer?: JSX.Element | null + renderConversationMsgIsRead?: () => void + conversationNameRenderer?: JSX.Element | null + conversationMsgRenderer?: JSX.Element | null beMentioned?: boolean prefix?: string commonPrefix?: string @@ -30,17 +31,17 @@ export interface ConversationItemProps { export const ConversationItem: FC<ConversationItemProps> = ({ isTop, isMute, - sessionName, + conversationName, menuRenderer, avatarRenderer, time, - lastMsg, + lastMessage, beMentioned = false, isSelected = false, onItemClick, - sessionMsgRenderer, - sessionNameRenderer, - renderSessionMsgIsRead, + conversationMsgRenderer, + conversationNameRenderer, + renderConversationMsgIsRead, prefix = 'conversation', commonPrefix = 'common', }) => { @@ -58,19 +59,37 @@ export const ConversationItem: FC<ConversationItemProps> = ({ const { t } = useTranslation() const msg = useMemo(() => { - const { type = '', body = '', status } = lastMsg || {} + const { + messageType, + text = '', + lastMessageState, + sendingState, + } = lastMessage || {} - if (!type) { + if ( + lastMessageState === + V2NIMConst.V2NIMLastMessageState.V2NIM_MESSAGE_STATUS_REVOKE + ) { + return t('recallMessageText') + } + if (messageType === void 0) { return '' } - if (status === 'sending') { + if ( + sendingState === + V2NIMConst.V2NIMMessageSendingState.V2NIM_MESSAGE_SENDING_STATE_SENDING + ) { return '' } - if (status === 'sendFailed' || status === 'refused') { + if ( + sendingState === + V2NIMConst.V2NIMMessageSendingState.V2NIM_MESSAGE_SENDING_STATE_FAILED + ) { return <ExclamationCircleFilled style={{ color: 'red' }} /> } - return lastMsg ? getMsgContentTipByType(lastMsg, t) : '' - }, [lastMsg, t]) + + return lastMessage ? getMsgContentTipByType({ messageType, text }, t) : '' + }, [lastMessage, t]) return ( <Dropdown overlay={menuRenderer} trigger={['contextMenu']}> @@ -83,7 +102,7 @@ export const ConversationItem: FC<ConversationItemProps> = ({ {avatarRenderer} <div className={`${prefix}-item-content`}> <div className={`${prefix}-item-content-name`}> - {sessionNameRenderer ?? sessionName} + {conversationNameRenderer ?? conversationName} </div> <div className={`${prefix}-item-content-msg`}> {beMentioned && ( @@ -91,9 +110,9 @@ export const ConversationItem: FC<ConversationItemProps> = ({ {t('beMentioned')} </span> )} - {renderSessionMsgIsRead?.()} + {renderConversationMsgIsRead?.()} <div className={`${prefix}-item-content-msg-body`}> - {sessionMsgRenderer ?? msg} + {conversationMsgRenderer ?? msg} </div> </div> </div> diff --git a/react/src/YXUIKit/im-kit-ui/src/conversation/components/ConversationList.tsx b/react/src/YXUIKit/im-kit-ui/src/conversation/components/ConversationList.tsx index b79f21d..26e5815 100644 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/components/ConversationList.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/conversation/components/ConversationList.tsx @@ -1,47 +1,50 @@ import React from 'react' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' import { P2PItem } from './P2PItem' import { GroupItem } from './GroupItem' import { Spin, Empty } from 'antd' +import { V2NIMConversationForUI } from '@xkit-yx/im-store-v2/dist/types/types' +import { V2NIMConst } from 'nim-web-sdk-ng' export type ConversationCallbackProps = { - onSessionItemClick: (session: NimKitCoreTypes.ISession) => void - onSessionItemDeleteClick: (session: NimKitCoreTypes.ISession) => void - onSessionItemStickTopChange: ( - session: NimKitCoreTypes.ISession, + onConversationItemClick: (conversation: V2NIMConversationForUI) => void + onConversationItemDeleteClick: (conversation: V2NIMConversationForUI) => void + onConversationItemStickTopChange: ( + conversation: V2NIMConversationForUI, isTop: boolean ) => void - onSessionItemMuteChange: ( - session: NimKitCoreTypes.ISession, + onConversationItemMuteChange: ( + conversation: V2NIMConversationForUI, mute: boolean ) => void } export type ConversationListProps = { - sessions: NimKitCoreTypes.ISession[] + conversations: V2NIMConversationForUI[] loading?: boolean - selectedSession?: string - renderCustomTeamSession?: ( - options: { session: NimKitCoreTypes.TeamSession } & Omit< + selectedConversation?: string + renderCustomTeamConversation?: ( + options: { conversation: V2NIMConversationForUI } & Omit< ConversationCallbackProps, - 'onSessionItemMuteChange' + 'onConversationItemMuteChange' > ) => JSX.Element | null | undefined - renderCustomP2pSession?: ( - options: { session: NimKitCoreTypes.P2PSession } & ConversationCallbackProps + renderCustomP2pConversation?: ( + options: { + conversation: V2NIMConversationForUI + } & ConversationCallbackProps ) => JSX.Element | null | undefined - renderSessionListEmpty?: () => JSX.Element | null | undefined - renderSessionName?: (options: { - session: NimKitCoreTypes.ISession + renderConversationListEmpty?: () => JSX.Element | null | undefined + renderConversationName?: (options: { + conversation: V2NIMConversationForUI }) => JSX.Element | null | undefined - renderSessionMsg?: (options: { - session: NimKitCoreTypes.ISession + renderConversationMsg?: (options: { + conversation: V2NIMConversationForUI }) => JSX.Element | null | undefined - renderP2pSessionAvatar?: (options: { - session: NimKitCoreTypes.ISession + renderP2pConversationAvatar?: (options: { + conversation: V2NIMConversationForUI }) => JSX.Element | null | undefined - renderTeamSessionAvatar?: (options: { - session: NimKitCoreTypes.ISession + renderTeamConversationAvatar?: (options: { + conversation: V2NIMConversationForUI }) => JSX.Element | null | undefined prefix?: string @@ -49,20 +52,20 @@ export type ConversationListProps = { } & ConversationCallbackProps export const ConversationList: React.FC<ConversationListProps> = ({ - sessions, + conversations, loading = false, - selectedSession, - onSessionItemClick, - onSessionItemDeleteClick, - onSessionItemStickTopChange, - onSessionItemMuteChange, - renderCustomP2pSession, - renderCustomTeamSession, - renderSessionListEmpty, - renderP2pSessionAvatar, - renderTeamSessionAvatar, - renderSessionMsg, - renderSessionName, + selectedConversation, + onConversationItemClick, + onConversationItemDeleteClick, + onConversationItemStickTopChange, + onConversationItemMuteChange, + renderCustomP2pConversation, + renderCustomTeamConversation, + renderConversationListEmpty, + renderP2pConversationAvatar, + renderTeamConversationAvatar, + renderConversationMsg, + renderConversationName, prefix = 'conversation', commonPrefix = 'common', }) => { @@ -70,65 +73,78 @@ export const ConversationList: React.FC<ConversationListProps> = ({ <div className={`${prefix}-list-wrapper`}> {loading ? ( <Spin /> - ) : !sessions.length ? ( - renderSessionListEmpty?.() ?? <Empty style={{ marginTop: 10 }} /> + ) : !conversations.length ? ( + renderConversationListEmpty?.() ?? <Empty style={{ marginTop: 10 }} /> ) : ( - sessions.map((item) => { - return item.scene === 'p2p' - ? renderCustomP2pSession?.({ - session: item as NimKitCoreTypes.P2PSession, - onSessionItemClick, - onSessionItemDeleteClick, - onSessionItemMuteChange, - onSessionItemStickTopChange, + conversations.map((item) => { + return item.type === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P + ? renderCustomP2pConversation?.({ + conversation: item, + onConversationItemClick, + onConversationItemDeleteClick, + onConversationItemMuteChange, + onConversationItemStickTopChange, }) ?? ( <P2PItem - {...(item as NimKitCoreTypes.P2PSession)} - key={item.id} + {...item} + key={item.conversationId} prefix={prefix} commonPrefix={commonPrefix} - isSelected={selectedSession === item.id} + isSelected={selectedConversation === item.conversationId} onItemClick={() => { - onSessionItemClick(item) + onConversationItemClick(item) }} onDeleteClick={() => { - onSessionItemDeleteClick(item) + onConversationItemDeleteClick(item) }} onStickTopChange={(isTop) => { - onSessionItemStickTopChange(item, isTop) + onConversationItemStickTopChange(item, isTop) }} onMuteChange={(mute) => { - onSessionItemMuteChange(item, mute) + onConversationItemMuteChange(item, mute) }} - sessionNameRenderer={renderSessionName?.({ session: item })} - sessionMsgRenderer={renderSessionMsg?.({ session: item })} - avatarRenderer={renderP2pSessionAvatar?.({ session: item })} + conversationNameRenderer={renderConversationName?.({ + conversation: item, + })} + conversationMsgRenderer={renderConversationMsg?.({ + conversation: item, + })} + avatarRenderer={renderP2pConversationAvatar?.({ + conversation: item, + })} /> ) - : renderCustomTeamSession?.({ - session: item as NimKitCoreTypes.TeamSession, - onSessionItemClick, - onSessionItemDeleteClick, - onSessionItemStickTopChange, + : renderCustomTeamConversation?.({ + conversation: item, + onConversationItemClick, + onConversationItemDeleteClick, + onConversationItemStickTopChange, }) ?? ( <GroupItem - {...(item as NimKitCoreTypes.TeamSession)} - key={item.id} + {...item} + key={item.conversationId} prefix={prefix} commonPrefix={commonPrefix} - isSelected={selectedSession === item.id} + isSelected={selectedConversation === item.conversationId} onItemClick={() => { - onSessionItemClick(item) + onConversationItemClick(item) }} onDeleteClick={() => { - onSessionItemDeleteClick(item) + onConversationItemDeleteClick(item) }} onStickTopChange={(isTop) => { - onSessionItemStickTopChange(item, isTop) + onConversationItemStickTopChange(item, isTop) }} - sessionNameRenderer={renderSessionName?.({ session: item })} - sessionMsgRenderer={renderSessionMsg?.({ session: item })} - avatarRenderer={renderTeamSessionAvatar?.({ session: item })} + conversationNameRenderer={renderConversationName?.({ + conversation: item, + })} + conversationMsgRenderer={renderConversationMsg?.({ + conversation: item, + })} + avatarRenderer={renderTeamConversationAvatar?.({ + conversation: item, + })} /> ) }) diff --git a/react/src/YXUIKit/im-kit-ui/src/conversation/components/GroupItem.tsx b/react/src/YXUIKit/im-kit-ui/src/conversation/components/GroupItem.tsx index 808209c..60b3afd 100644 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/components/GroupItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/conversation/components/GroupItem.tsx @@ -1,18 +1,22 @@ import React, { FC, useMemo } from 'react' import { Menu } from 'antd' -import { CrudeAvatar, CommonIcon, useTranslation } from '../../common' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' +import { + CrudeAvatar, + CommonIcon, + useTranslation, + useStateContext, +} from '../../common' import { ConversationItem } from './ConversationItem' +import { V2NIMConversationForUI } from '@xkit-yx/im-store-v2/dist/types/types' -export interface GroupItemProps extends NimKitCoreTypes.TeamSession { +export interface GroupItemProps extends V2NIMConversationForUI { isSelected: boolean onStickTopChange: (isTop: boolean) => void onDeleteClick: () => void onItemClick: () => void - beMentioned?: boolean avatarRenderer?: JSX.Element | null - sessionNameRenderer?: JSX.Element | null - sessionMsgRenderer?: JSX.Element | null + conversationNameRenderer?: JSX.Element | null + conversationMsgRenderer?: JSX.Element | null prefix?: string commonPrefix?: string } @@ -20,31 +24,33 @@ export interface GroupItemProps extends NimKitCoreTypes.TeamSession { export const GroupItem: FC<GroupItemProps> = ({ onStickTopChange, onDeleteClick, - teamId, + conversationId, name, avatar, - unread, - lastMsg, + unreadCount, + lastMessage, beMentioned, - stickTopInfo, + stickTop, updateTime, isSelected, onItemClick, avatarRenderer, - sessionNameRenderer, - sessionMsgRenderer, + conversationNameRenderer, + conversationMsgRenderer, prefix = 'conversation', commonPrefix = 'common', }) => { + const { nim } = useStateContext() const { t } = useTranslation() + const teamId = + nim.V2NIMConversationIdUtil.parseConversationTargetId(conversationId) + const menuRenderer = useMemo(() => { const items = [ { - label: stickTopInfo?.isStickOnTop - ? t('deleteStickTopText') - : t('addStickTopText'), - icon: stickTopInfo?.isStickOnTop ? ( + label: stickTop ? t('deleteStickTopText') : t('addStickTopText'), + icon: stickTop ? ( <CommonIcon type="icon-quxiaozhiding" /> ) : ( <CommonIcon type="icon-xiaoxizhiding" /> @@ -63,7 +69,7 @@ export const GroupItem: FC<GroupItemProps> = ({ { label: t('deleteSessionText'), icon: <CommonIcon type="icon-shanchu" />, - key: 'deleteSession', + key: 'deleteConversation', }, ] as any @@ -73,9 +79,9 @@ export const GroupItem: FC<GroupItemProps> = ({ domEvent.stopPropagation() switch (key) { case 'stickTop': - onStickTopChange(!stickTopInfo?.isStickOnTop) + onStickTopChange(!stickTop) break - case 'deleteSession': + case 'deleteConversation': onDeleteClick() break default: @@ -85,30 +91,30 @@ export const GroupItem: FC<GroupItemProps> = ({ items={items} ></Menu> ) - }, [stickTopInfo?.isStickOnTop, onStickTopChange, onDeleteClick, t]) + }, [stickTop, onStickTopChange, onDeleteClick, t]) return ( <ConversationItem - isTop={!!stickTopInfo?.isStickOnTop} + isTop={stickTop} isMute={false} - sessionName={name || teamId} - time={lastMsg?.time || updateTime} - lastMsg={lastMsg} + conversationName={name || teamId} + time={lastMessage?.messageRefer.createTime || updateTime} + lastMessage={lastMessage} beMentioned={beMentioned} isSelected={isSelected} onItemClick={onItemClick} prefix={prefix} commonPrefix={commonPrefix} menuRenderer={menuRenderer} - sessionMsgRenderer={sessionMsgRenderer} - sessionNameRenderer={sessionNameRenderer} + conversationMsgRenderer={conversationMsgRenderer} + conversationNameRenderer={conversationNameRenderer} avatarRenderer={ avatarRenderer ?? ( <CrudeAvatar nick={name} account={teamId} avatar={avatar} - count={isSelected ? 0 : unread} + count={isSelected ? 0 : unreadCount} /> ) } diff --git a/react/src/YXUIKit/im-kit-ui/src/conversation/components/P2PItem.tsx b/react/src/YXUIKit/im-kit-ui/src/conversation/components/P2PItem.tsx index d7da4c3..d6fc9c8 100644 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/components/P2PItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/conversation/components/P2PItem.tsx @@ -7,20 +7,21 @@ import { useStateContext, ReadPercent, } from '../../common' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' import { ConversationItem } from './ConversationItem' import { CheckCircleOutlined } from '@ant-design/icons' import { observer } from 'mobx-react' +import { V2NIMConversationForUI } from '@xkit-yx/im-store-v2/dist/types/types' +import { V2NIMConst } from 'nim-web-sdk-ng' -export interface P2PItemProps extends NimKitCoreTypes.P2PSession { +export interface P2PItemProps extends V2NIMConversationForUI { isSelected: boolean onStickTopChange: (isTop: boolean) => void onDeleteClick: () => void onMuteChange: (mute: boolean) => void onItemClick: () => void avatarRenderer?: JSX.Element | null - sessionNameRenderer?: JSX.Element | null - sessionMsgRenderer?: JSX.Element | null + conversationNameRenderer?: JSX.Element | null + conversationMsgRenderer?: JSX.Element | null msgReceiptTime?: number prefix?: string @@ -33,31 +34,31 @@ export const P2PItem: FC<P2PItemProps> = observer( onDeleteClick, onMuteChange, onItemClick, - isMute, - stickTopInfo, - to, - unread, - lastMsg, + conversationId, + mute = false, + stickTop, + lastMessage, + unreadCount, updateTime, isSelected, avatarRenderer, - sessionMsgRenderer, - sessionNameRenderer, + conversationMsgRenderer, + conversationNameRenderer, prefix = 'conversation', commonPrefix = 'common', msgReceiptTime, - ...props }) => { const { t } = useTranslation() - const { store, localOptions } = useStateContext() + const { nim, store, localOptions } = useStateContext() + + const to = + nim.V2NIMConversationIdUtil.parseConversationTargetId(conversationId) const menuRenderer = useMemo(() => { const items = [ { - label: stickTopInfo?.isStickOnTop - ? t('deleteStickTopText') - : t('addStickTopText'), - icon: stickTopInfo?.isStickOnTop ? ( + label: stickTop ? t('deleteStickTopText') : t('addStickTopText'), + icon: stickTop ? ( <CommonIcon type="icon-quxiaozhiding" /> ) : ( <CommonIcon type="icon-xiaoxizhiding" /> @@ -65,18 +66,18 @@ export const P2PItem: FC<P2PItemProps> = observer( key: 'stickTop', }, { - label: isMute ? t('unmuteSessionText') : t('muteSessionText'), - icon: isMute ? ( + label: mute ? t('unmuteSessionText') : t('muteSessionText'), + icon: mute ? ( <CommonIcon type="icon-quxiaoxiaoximiandarao" /> ) : ( <CommonIcon type="icon-xiaoximiandarao" /> ), - key: 'muteSession', + key: 'muteConversation', }, { label: t('deleteSessionText'), icon: <CommonIcon type="icon-shanchu" />, - key: 'deleteSession', + key: 'deleteConversation', }, ] as any @@ -86,12 +87,12 @@ export const P2PItem: FC<P2PItemProps> = observer( domEvent.stopPropagation() switch (key) { case 'stickTop': - onStickTopChange(!stickTopInfo?.isStickOnTop) + onStickTopChange(!stickTop) break - case 'muteSession': - onMuteChange(!isMute) + case 'muteConversation': + onMuteChange(!mute) break - case 'deleteSession': + case 'deleteConversation': onDeleteClick() break default: @@ -101,22 +102,25 @@ export const P2PItem: FC<P2PItemProps> = observer( items={items} ></Menu> ) - }, [ - isMute, - stickTopInfo?.isStickOnTop, - onStickTopChange, - onDeleteClick, - onMuteChange, - t, - ]) + }, [mute, stickTop, onStickTopChange, onDeleteClick, onMuteChange, t]) const renderSessionMsgIsRead = () => { return localOptions.p2pMsgReceiptVisible && - lastMsg?.flow === 'out' && - lastMsg?.type !== 'g2' && - lastMsg?.type !== 'notification' ? ( + lastMessage?.messageRefer.senderId === + store.userStore.myUserInfo.accountId && + lastMessage?.messageType !== + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL && + lastMessage?.messageType !== + V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_NOTIFICATION && + lastMessage?.sendingState === + V2NIMConst.V2NIMMessageSendingState + .V2NIM_MESSAGE_SENDING_STATE_SUCCEEDED && + lastMessage?.lastMessageState !== + V2NIMConst.V2NIMLastMessageState.V2NIM_MESSAGE_STATUS_REVOKE ? ( <div className={`${prefix}-item-content-read-status`}> - {(msgReceiptTime ?? 0) - (lastMsg?.time ?? 0) > 0 ? ( + {(msgReceiptTime ?? 0) - + (lastMessage?.messageRefer.createTime ?? 0) >= + 0 ? ( <CheckCircleOutlined className={`${prefix}-item-content-read-icon`} /> @@ -129,27 +133,27 @@ export const P2PItem: FC<P2PItemProps> = observer( return ( <ConversationItem - isTop={!!stickTopInfo?.isStickOnTop} - isMute={isMute} - sessionName={store.uiStore.getAppellation({ account: to })} - time={lastMsg?.time || updateTime} - lastMsg={lastMsg} + isTop={stickTop} + isMute={mute} + conversationName={store.uiStore.getAppellation({ account: to })} + time={lastMessage?.messageRefer.createTime || updateTime} + lastMessage={lastMessage} isSelected={isSelected} onItemClick={onItemClick} menuRenderer={menuRenderer} prefix={prefix} commonPrefix={commonPrefix} - sessionMsgRenderer={sessionMsgRenderer} - sessionNameRenderer={sessionNameRenderer} - renderSessionMsgIsRead={renderSessionMsgIsRead} + conversationMsgRenderer={conversationMsgRenderer} + conversationNameRenderer={conversationNameRenderer} + renderConversationMsgIsRead={renderSessionMsgIsRead} avatarRenderer={ avatarRenderer ?? ( <ComplexAvatarContainer account={to} prefix={commonPrefix} canClick={false} - count={isSelected ? 0 : unread} - dot={isSelected ? false : isMute && unread > 0} + count={isSelected ? 0 : unreadCount} + dot={isSelected ? false : mute && unreadCount > 0} /> ) } diff --git a/react/src/YXUIKit/im-kit-ui/src/index.ts b/react/src/YXUIKit/im-kit-ui/src/index.ts index 7ee6d2c..f624de7 100644 --- a/react/src/YXUIKit/im-kit-ui/src/index.ts +++ b/react/src/YXUIKit/im-kit-ui/src/index.ts @@ -26,8 +26,6 @@ import { UseEventTrackingProps, } from './common' -import { NimKitCoreTypes, NimKitCoreFactory } from '@xkit-yx/core-kit' - import { ConversationContainer } from './conversation' import { ContactListContainer, @@ -38,14 +36,15 @@ import { } from './contact' import { ChatContainer, ChatMessageItem } from './chat' import { AddContainer, SearchContainer } from './search' -import RootStore from '@xkit-yx/im-store' -import { NIMInitializeOptions } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/NIMInterface' +import RootStore from '@xkit-yx/im-store-v2' +import V2NIM from 'nim-web-sdk-ng' +import { LocalOptions } from '@xkit-yx/im-store-v2/dist/types/types' export class IMUIKit { get context(): { - nim: NimKitCoreTypes.INimKitCore + nim: V2NIM store: RootStore - initOptions: NIMInitializeOptions + localOptions: LocalOptions } | void { // @ts-ignore return window.__xkit_store__ @@ -83,22 +82,18 @@ export class IMUIKit { } getStateContext(): { - nim: NimKitCoreTypes.INimKitCore + nim: V2NIM store: RootStore - initOptions: NIMInitializeOptions + localOptions: LocalOptions } | void { return this.context } destroy(): void { this.context?.store.destroy() - this.context?.nim.destroy() - const NIM = NimKitCoreFactory(this.providerProps.sdkVersion || 1) // @ts-ignore RootStore.ins = void 0 - //@ts-ignore - NIM.ins = void 0 } } @@ -124,8 +119,6 @@ export { useStateContext, useTranslation, useEventTracking, - NimKitCoreTypes, - NimKitCoreFactory, ConversationContainer, ContactListContainer, BlackListContainer, diff --git a/react/src/YXUIKit/im-kit-ui/src/search/add/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/search/add/Container.tsx index 766b802..72ceb01 100644 --- a/react/src/YXUIKit/im-kit-ui/src/search/add/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/search/add/Container.tsx @@ -6,6 +6,7 @@ import JoinTeamModal from './components/JoinTeamModal' import CreateModal from './components/CreateModal' import packageJson from '../../../package.json' import { observer } from 'mobx-react' +import sdkPkg from 'nim-web-sdk-ng/package.json' export type PanelScene = 'addFriend' | 'joinTeam' | 'createTeam' @@ -28,13 +29,13 @@ export interface AddContainerProps { export const AddContainer: React.FC<AddContainerProps> = observer( ({ onClickChat, prefix = 'search', commonPrefix = 'common' }) => { - const { nim, initOptions } = useStateContext() + const { nim } = useStateContext() useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'SearchUIKit', - imVersion: nim.version, + imVersion: sdkPkg.version, }) const [addFriendModalVisible, setAddFriendModalVisible] = useState(false) diff --git a/react/src/YXUIKit/im-kit-ui/src/search/add/components/AddFriendModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/search/add/components/AddFriendModal/index.tsx index cc5d85e..90941a2 100644 --- a/react/src/YXUIKit/im-kit-ui/src/search/add/components/AddFriendModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/search/add/components/AddFriendModal/index.tsx @@ -6,8 +6,9 @@ import { } from '../../../../common' import React, { useState } from 'react' import { CrudeAvatar } from '../../../../common' -import { UserNameCard } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/UserServiceInterface' +import { V2NIMUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMUserService' import { observer } from 'mobx-react' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface AddFriendModalProps { visible: boolean @@ -33,7 +34,7 @@ const AddFriendModal: React.FC<AddFriendModalProps> = observer( const [searchValue, setSearchValue] = useState('') const [searchRes, setSearchRes] = useState< - (UserNameCard & { alias?: string }) | undefined + (V2NIMUser & { alias?: string }) | undefined >(undefined) const [searchResEmpty, setSearchResEmpty] = useState(false) const [searching, setSearching] = useState(false) @@ -66,17 +67,23 @@ const AddFriendModal: React.FC<AddFriendModalProps> = observer( if (searchRes) { setAdding(true) if (localOptions?.addFriendNeedVerify) { - await store.friendStore.applyFriendActive(searchRes.account) + await store.friendStore.addFriendActive(searchRes.accountId, { + addMode: + V2NIMConst.V2NIMFriendAddMode.V2NIM_FRIEND_MODE_TYPE_APPLY, + postscript: '', + }) message.success(t('applyFriendSuccessText')) } else { - await store.friendStore.addFriendActive(searchRes.account) + await store.friendStore.addFriendActive(searchRes.accountId, { + addMode: V2NIMConst.V2NIMFriendAddMode.V2NIM_FRIEND_MODE_TYPE_ADD, + postscript: '', + }) message.success(t('addFriendSuccessText')) } // 发送申请或添加好友成功后解除黑名单 - await store.relationStore.setBlackActive({ - account: searchRes.account, - isAdd: false, - }) + await store.relationStore.removeUserFromBlockListActive( + searchRes.accountId + ) } setAdding(false) } catch (error) { @@ -86,8 +93,11 @@ const AddFriendModal: React.FC<AddFriendModalProps> = observer( const handleChat = async () => { if (searchRes) { - await store.sessionStore.insertSessionActive('p2p', searchRes.account) - onChat(searchRes.account) + await store.conversationStore.insertConversationActive( + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P, + searchRes.accountId + ) + onChat(searchRes.accountId) resetState() } } @@ -137,18 +147,18 @@ const AddFriendModal: React.FC<AddFriendModalProps> = observer( <div className={`${_prefix}-content`}> <CrudeAvatar avatar={searchRes.avatar} - nick={searchRes.nick} - account={searchRes.account} + nick={searchRes.name} + account={searchRes.accountId} /> <div className={`${_prefix}-info`}> <div className={`${_prefix}-info-name`}> - {searchRes.nick || searchRes.account} + {searchRes.name || searchRes.accountId} </div> <div className={`${_prefix}-info-account`}> - {searchRes.account} + {searchRes.accountId} </div> </div> - {store.uiStore.getRelation(searchRes.account).relation !== + {store.uiStore.getRelation(searchRes.accountId).relation !== 'stranger' ? ( <Button type="primary" onClick={handleChat}> {t('chatButtonText')} diff --git a/react/src/YXUIKit/im-kit-ui/src/search/add/components/CreateModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/search/add/components/CreateModal/index.tsx index 7a713c0..b2af62f 100644 --- a/react/src/YXUIKit/im-kit-ui/src/search/add/components/CreateModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/search/add/components/CreateModal/index.tsx @@ -7,7 +7,7 @@ import { useTranslation, useStateContext, } from '../../../../common' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface CreateModalProps { visible: boolean @@ -30,9 +30,7 @@ const CreateModal: React.FC<CreateModalProps> = ({ const { store } = useStateContext() - const [selectedList, setSelectedList] = useState< - NimKitCoreTypes.IFriendInfo[] - >([]) + const [selectedList, setSelectedList] = useState<string[]>([]) const [groupName, setGroupName] = useState('') const [avatarUrl, setAvatarUrl] = useState( urls[Math.floor(Math.random() * 5)] @@ -44,7 +42,7 @@ const CreateModal: React.FC<CreateModalProps> = ({ try { setCreating(true) const team = await store.teamStore.createTeamActive({ - accounts: selectedList.map((item) => item.account), + accounts: selectedList, avatar: avatarUrl, name: groupName.trim(), }) @@ -59,7 +57,10 @@ const CreateModal: React.FC<CreateModalProps> = ({ const handleChat = async () => { if (teamId) { - await store.sessionStore.insertSessionActive('team', teamId) + await store.conversationStore.insertConversationActive( + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM, + teamId + ) onChat(teamId) resetState() } @@ -138,11 +139,11 @@ const CreateModal: React.FC<CreateModalProps> = ({ {t('addTeamMemberText')} </div> <FriendSelectContainer - onSelect={(list) => { - setSelectedList(list) + onSelect={(accounts) => { + setSelectedList(accounts) }} max={10} - selectedAccounts={selectedList.map((item) => item.account)} + selectedAccounts={selectedList} prefix={commonPrefix} /> </div> diff --git a/react/src/YXUIKit/im-kit-ui/src/search/add/components/JoinTeamModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/search/add/components/JoinTeamModal/index.tsx index 206c9f6..00ec94f 100644 --- a/react/src/YXUIKit/im-kit-ui/src/search/add/components/JoinTeamModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/search/add/components/JoinTeamModal/index.tsx @@ -6,7 +6,9 @@ import { } from '../../../../common' import React, { useState } from 'react' import { CrudeAvatar } from '../../../../common' -import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { observer } from 'mobx-react' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface JoinTeamModalProps { visible: boolean @@ -16,153 +18,154 @@ export interface JoinTeamModalProps { commonPrefix?: string } -const JoinTeamModal: React.FC<JoinTeamModalProps> = ({ - visible, - onCancel, - onChat, - prefix = 'search', - commonPrefix = 'common', -}) => { - const _prefix = `${prefix}-add-modal` +const JoinTeamModal: React.FC<JoinTeamModalProps> = observer( + ({ + visible, + onCancel, + onChat, + prefix = 'search', + commonPrefix = 'common', + }) => { + const _prefix = `${prefix}-add-modal` - const { store } = useStateContext() + const { store } = useStateContext() - const { t } = useTranslation() + const { t } = useTranslation() - const [searchValue, setSearchValue] = useState('') - const [inTeam, setInTeam] = useState(false) - const [searchRes, setSearchRes] = useState<Team | undefined>(undefined) - const [searchResEmpty, setSearchResEmpty] = useState(false) - const [searching, setSearching] = useState(false) - const [adding, setAdding] = useState(false) + const [searchValue, setSearchValue] = useState('') + const [searchRes, setSearchRes] = useState<V2NIMTeam | undefined>(undefined) + const [searchResEmpty, setSearchResEmpty] = useState(false) + const [searching, setSearching] = useState(false) + const [adding, setAdding] = useState(false) - const handleChange = (val: string) => { - setSearchValue(val) - setSearchResEmpty(false) - setSearchRes(undefined) - } + const inTeam = store.teamStore.teams.get(searchRes?.teamId || '') + + const handleChange = (val: string) => { + setSearchValue(val) + setSearchResEmpty(false) + setSearchRes(undefined) + } - const handleSearch = async () => { - try { - setSearching(true) - const { team, inTeam } = await store.uiStore.getTeamAndRelation( - searchValue - ) - if (!team) { + const handleSearch = async () => { + try { + setSearching(true) + const team = await store.teamStore.getTeamForceActive(searchValue) + if (!team) { + setSearchResEmpty(true) + } else { + setSearchRes(team) + } + setSearching(false) + } catch (error) { setSearchResEmpty(true) - } else { - setInTeam(inTeam) - setSearchRes(team) + setSearching(false) } - setSearching(false) - } catch (error) { - setSearchResEmpty(true) - setSearching(false) } - } - const handleAdd = async () => { - try { - if (searchRes) { - if (searchRes.type === 'normal') { - message.error(t('notSupportJoinText')) - return - } - setAdding(true) - await store.teamStore.applyTeamActive(searchRes.teamId) - if (store.teamStore.teams.get(searchRes.teamId)) { - setInTeam(true) + const handleAdd = async () => { + try { + if (searchRes) { + if ( + searchRes.teamType === + V2NIMConst.V2NIMTeamType.V2NIM_TEAM_TYPE_INVALID + ) { + message.error(t('notSupportJoinText')) + return + } + setAdding(true) + await store.teamStore.applyTeamActive(searchRes.teamId) + // 目前没有申请加入群组,直接写死 message.success(t('joinTeamSuccessText')) - } else { - message.success(t('applyTeamSuccessText')) } + setAdding(false) + } catch (error) { + setAdding(false) } - setAdding(false) - } catch (error) { - setAdding(false) } - } - const handleChat = async () => { - if (searchRes) { - await store.sessionStore.insertSessionActive('team', searchRes.teamId) - onChat(searchRes.teamId) - resetState() + const handleChat = async () => { + if (searchRes) { + await store.conversationStore.insertConversationActive( + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM, + searchRes.teamId + ) + onChat(searchRes.teamId) + resetState() + } } - } - const handleCancel = () => { - onCancel() - resetState() - } + const handleCancel = () => { + onCancel() + resetState() + } - const resetState = () => { - setSearchValue('') - setInTeam(false) - setSearchRes(undefined) - setSearchResEmpty(false) - setSearching(false) - setAdding(false) - } + const resetState = () => { + setSearchValue('') + setSearchRes(undefined) + setSearchResEmpty(false) + setSearching(false) + setAdding(false) + } - const renderFooter = () => ( - <div className={`${_prefix}-footer`}> - <Button onClick={handleCancel}>{t('cancelText')}</Button> - <Button loading={searching} onClick={handleSearch} type="primary"> - {t('searchButtonText')} - </Button> - </div> - ) + const renderFooter = () => ( + <div className={`${_prefix}-footer`}> + <Button onClick={handleCancel}>{t('cancelText')}</Button> + <Button loading={searching} onClick={handleSearch} type="primary"> + {t('searchButtonText')} + </Button> + </div> + ) - return ( - <div> - <Modal - className={_prefix} - title={t('joinTeamText')} - onCancel={handleCancel} - visible={visible} - width={460} - footer={!searchRes ? renderFooter() : null} - > - <SearchInput - placeholder={t('teamIdPlaceholder')} - prefix={commonPrefix} - onChange={handleChange} - value={searchValue} - /> - {searchResEmpty ? ( - <div className={`${_prefix}-empty-content`}> - {t('teamIdNotMatchText')} - </div> - ) : searchRes ? ( - <div className={`${_prefix}-content`}> - <CrudeAvatar - avatar={searchRes.avatar} - nick={searchRes.name} - account={searchRes.teamId} - /> - <div className={`${_prefix}-info`}> - <div className={`${_prefix}-info-name`}> - {searchRes.name || searchRes.teamId} - </div> - <div className={`${_prefix}-info-account`}> - {searchRes.teamId} + return ( + <div> + <Modal + className={_prefix} + title={t('joinTeamText')} + onCancel={handleCancel} + visible={visible} + width={460} + footer={!searchRes ? renderFooter() : null} + > + <SearchInput + placeholder={t('teamIdPlaceholder')} + prefix={commonPrefix} + onChange={handleChange} + value={searchValue} + /> + {searchResEmpty ? ( + <div className={`${_prefix}-empty-content`}> + {t('teamIdNotMatchText')} + </div> + ) : searchRes ? ( + <div className={`${_prefix}-content`}> + <CrudeAvatar + avatar={searchRes.avatar} + nick={searchRes.name} + account={searchRes.teamId} + /> + <div className={`${_prefix}-info`}> + <div className={`${_prefix}-info-name`}> + {searchRes.name || searchRes.teamId} + </div> + <div className={`${_prefix}-info-account`}> + {searchRes.teamId} + </div> </div> + {inTeam ? ( + <Button type="primary" onClick={handleChat}> + {t('chatButtonText')} + </Button> + ) : ( + <Button loading={adding} type="primary" onClick={handleAdd}> + {t('addButtonText')} + </Button> + )} </div> - {inTeam ? ( - <Button type="primary" onClick={handleChat}> - {t('chatButtonText')} - </Button> - ) : ( - <Button loading={adding} type="primary" onClick={handleAdd}> - {t('addButtonText')} - </Button> - )} - </div> - ) : null} - </Modal> - </div> - ) -} + ) : null} + </Modal> + </div> + ) + } +) export default JoinTeamModal diff --git a/react/src/YXUIKit/im-kit-ui/src/search/search/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/search/search/Container.tsx index aac7b58..29dbb10 100644 --- a/react/src/YXUIKit/im-kit-ui/src/search/search/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/search/search/Container.tsx @@ -1,13 +1,16 @@ import React, { useState } from 'react' import { useTranslation, useEventTracking, useStateContext } from '../../common' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' import { SearchOutlined } from '@ant-design/icons' import SearchModal from './components/SearchModal' import { SectionListItem } from './components/SearchModal' -import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' import packageJson from '../../../package.json' -import { TMsgScene } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/MsgServiceInterface' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMConversationType } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +import { V2NIMFriend } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMFriendService' +import { V2NIMUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMUserService' import { observer } from 'mobx-react' +import sdkPkg from 'nim-web-sdk-ng/package.json' +import { V2NIMConst } from 'nim-web-sdk-ng' export interface SearchContainerProps { /** @@ -44,33 +47,38 @@ export const SearchContainer: React.FC<SearchContainerProps> = observer( renderEmpty, renderSearchResultEmpty, }) => { - const { nim, store, initOptions } = useStateContext() + const { nim, store } = useStateContext() const { t } = useTranslation() useEventTracking({ - appkey: initOptions.appkey, + appkey: nim.options.appkey, version: packageJson.version, component: 'SearchUIKit', - imVersion: nim.version, + imVersion: sdkPkg.version, }) const [visible, setVisible] = useState(false) const handleChat = async (item: SectionListItem) => { - let scene: TMsgScene - let to = '' - if ((item as NimKitCoreTypes.IFriendInfo).account) { - scene = 'p2p' - to = (item as NimKitCoreTypes.IFriendInfo).account - } else if ((item as Team).teamId) { - scene = 'team' - to = (item as Team).teamId + let conversationType: V2NIMConversationType + let receiverId = '' + if ((item as V2NIMFriend & V2NIMUser).accountId) { + conversationType = + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P + receiverId = (item as V2NIMFriend & V2NIMUser).accountId + } else if ((item as V2NIMTeam).teamId) { + conversationType = + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM + receiverId = (item as V2NIMTeam).teamId } else { throw Error('unknow scene') } - await store.sessionStore.insertSessionActive(scene, to) + await store.conversationStore.insertConversationActive( + conversationType, + receiverId + ) setVisible(false) onClickChat?.() } @@ -81,6 +89,20 @@ export const SearchContainer: React.FC<SearchContainerProps> = observer( const _prefix = `${prefix}-search` + const friendsWithoutBlacklist = store.uiStore.friends + .filter((item) => !store.relationStore.blacklist.includes(item.accountId)) + .map((item) => { + const user: V2NIMUser = store.userStore.users.get(item.accountId) || { + accountId: '', + name: '', + createTime: Date.now(), + } + return { + ...item, + ...user, + } + }) + return ( <div className={`${_prefix}-wrapper`}> <div className={`${_prefix}-wrapper-button`} onClick={handleOpenModal}> @@ -91,7 +113,7 @@ export const SearchContainer: React.FC<SearchContainerProps> = observer( </div> <SearchModal visible={visible} - friends={store.uiStore.friendsWithoutBlacklist} + friends={friendsWithoutBlacklist} teams={store.uiStore.teamList} onCancel={() => setVisible(false)} prefix={prefix} diff --git a/react/src/YXUIKit/im-kit-ui/src/search/search/components/SearchModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/search/search/components/SearchModal/index.tsx index 0dc57df..0c3f6d4 100644 --- a/react/src/YXUIKit/im-kit-ui/src/search/search/components/SearchModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/search/search/components/SearchModal/index.tsx @@ -2,8 +2,9 @@ import React, { useState, useMemo } from 'react' import { Modal } from 'antd' import { SearchInput, CrudeAvatar, useTranslation } from '../../../../common' import { CrudeAvatarProps } from '../../../../common/components/CrudeAvatar' -import { NimKitCoreTypes } from '@xkit-yx/core-kit' -import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMFriend } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMFriendService' +import { V2NIMUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMUserService' export interface SearchItemProps extends CrudeAvatarProps { onClick: () => void @@ -20,7 +21,6 @@ const SearchItem: React.FC<SearchItemProps> = ({ return ( <div className={`${_prefix}-content-section-item`} onClick={onClick}> - {/* TODO CrudeAvatar 可以不用了,p2p 都用 ComplexAvatar */} <CrudeAvatar {...props} /> <span className={`${_prefix}-content-section-item-name`}> {props.alias || props.nick || props.account} @@ -29,7 +29,7 @@ const SearchItem: React.FC<SearchItemProps> = ({ ) } -export type SectionListItem = NimKitCoreTypes.IFriendInfo | Team +export type SectionListItem = (V2NIMFriend & V2NIMUser) | V2NIMTeam export type Section = { id: string @@ -39,8 +39,8 @@ export type Section = { export interface SearchModalProps { visible: boolean - friends: NimKitCoreTypes.IFriendInfo[] - teams: Team[] + friends: (V2NIMFriend & V2NIMUser)[] + teams: V2NIMTeam[] onCancel: () => void onResultItemClick: (item: SectionListItem) => void renderEmpty?: () => JSX.Element @@ -90,9 +90,9 @@ const SearchModal: React.FC<SearchModalProps> = ({ ...item, list: item.list.filter((item) => { return ( - (item as NimKitCoreTypes.IFriendInfo).alias || - (item as NimKitCoreTypes.IFriendInfo).nick || - (item as NimKitCoreTypes.IFriendInfo).account + (item as V2NIMFriend & V2NIMUser).alias || + (item as V2NIMFriend & V2NIMUser).name || + (item as V2NIMFriend & V2NIMUser).accountId ).includes(searchText) }), } @@ -101,9 +101,9 @@ const SearchModal: React.FC<SearchModalProps> = ({ return { ...item, list: item.list.filter((item) => { - return ((item as Team).name || (item as Team).teamId).includes( - searchText - ) + return ( + (item as V2NIMTeam).name || (item as V2NIMTeam).teamId + ).includes(searchText) }), } } @@ -167,17 +167,19 @@ const SearchModal: React.FC<SearchModalProps> = ({ </div> {section.list.map((item) => ( <SearchItem - // @ts-ignore - key={item.account || item.teamId} + key={ + (item as V2NIMFriend & V2NIMUser).accountId || + (item as V2NIMTeam).teamId + } onClick={() => handleItemClick(item)} prefix={prefix} - // @ts-ignore - account={item.account || item.teamId} + account={ + (item as V2NIMFriend & V2NIMUser).accountId || + (item as V2NIMTeam).teamId + } avatar={item.avatar} - // @ts-ignore - nick={item.nick || item.name} - // @ts-ignore - alias={item.alias || ''} + nick={item.name} + alias={(item as V2NIMFriend & V2NIMUser).alias || ''} /> ))} </div> diff --git a/react/src/YXUIKit/im-kit-ui/src/utils.ts b/react/src/YXUIKit/im-kit-ui/src/utils.ts index 61e2ede..f8d0dfb 100644 --- a/react/src/YXUIKit/im-kit-ui/src/utils.ts +++ b/react/src/YXUIKit/im-kit-ui/src/utils.ts @@ -1,4 +1,4 @@ -import logDebug from 'yunxin-log-debug' +import { logDebug } from '@xkit-yx/utils' import packageJson from '../package.json' export { logDebug } @@ -191,20 +191,6 @@ export const frequencyControl = < } } -/** - * 解析 sessionId,形如 scene-accid - */ -export const parseSessionId = ( - sessionId: string -): { scene: string; to: string } => { - const [scene, ...to] = sessionId.split('-') - return { - scene, - // 这样处理是为了防止有些用户 accid 中自带 - - to: to.join('-'), - } -} - interface IKeyMap { [key: string]: string } @@ -281,7 +267,7 @@ export const handleEmojiTranslate = (t) => { [t('Ambulance')]: 'icon-a-68', [t('Poop')]: 'icon-a-70', } - // TODO react-string-replace 的行为不是那么的好理解,比如像下面这个正则就一定要加 '()',后面最好干掉自己实现 + const INPUT_EMOJI_SYMBOL_REG = new RegExp( '(' + Object.keys(EMOJI_ICON_MAP_CONFIG) diff --git a/react/src/components/IMApp/index.tsx b/react/src/components/IMApp/index.tsx index c273899..8524a86 100644 --- a/react/src/components/IMApp/index.tsx +++ b/react/src/components/IMApp/index.tsx @@ -10,7 +10,7 @@ import { MyAvatarContainer, useStateContext, ComplexAvatarContainer, -} from '@xkit-yx/im-kit-ui' +} from '@xkit-yx/im-kit-ui/src' import { ConfigProvider, Badge, @@ -28,7 +28,7 @@ import en from './locales/en' import zh from './locales/zh' import classNames from 'classnames' import { observer } from 'mobx-react' -import '@xkit-yx/im-kit-ui/es/style' +import '@xkit-yx/im-kit-ui/src/style' import 'antd/es/badge/style' import './iconfont.css' import './index.less' @@ -44,13 +44,15 @@ import '@xkit-yx/call-kit-react-ui/es/style' import Calling from './components/call' //demo国际化函数 import { convertSecondsToTime, g2StatusMap, renderMsgDate, t } from './util' -import { parseSessionId } from '@xkit-yx/im-kit-ui/src/utils' -import { LocalOptions } from '@xkit-yx/im-store' import { DeleteOutlined } from '@ant-design/icons' import { pauseAllAudio, pauseAllVideo, } from '@xkit-yx/im-kit-ui/src/common/components/CommonParseSession' +import { LocalOptions } from '@xkit-yx/im-store-v2/dist/types/types' +import V2NIM, { V2NIMConst } from 'nim-web-sdk-ng' +import { RenderP2pCustomMessageOptions } from '@xkit-yx/im-kit-ui/src/chat/components/ChatP2pMessageList' + interface IMContainerProps { appkey: string //传入您的App Key account: string // 传入您的云信IM账号 @@ -65,23 +67,19 @@ interface IMAppProps { onLogout?: () => void locale: 'zh' | 'en' changeLanguage?: (value: 'zh' | 'en') => void - sdkVersion: 1 | 2 addFriendNeedVerify: boolean p2pMsgReceiptVisible: boolean teamMsgReceiptVisible: boolean needMention: boolean teamManagerVisible: boolean } -// @ts-ignore: Unreachable code error const IMApp: React.FC<IMAppProps> = observer((props) => { const { onLogout, locale, changeLanguage, - sdkVersion, addFriendNeedVerify, - account, appkey, p2pMsgReceiptVisible, teamMsgReceiptVisible, @@ -93,12 +91,16 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { const [isSettingModalOpen, setIsSettingModalOpen] = useState<boolean>(false) // 是否显示呼叫弹窗 const [callingVisible, setCallingVisible] = useState<boolean>(false) - // IMUIKit store实例 - const { store } = useStateContext() - // im sdk实例 - const nim = store.nim - // 当前选中会话场景和会话接受方 scene:p2p | team, to: accid - const { scene, to } = parseSessionId(store.uiStore.selectedSession) + // IMUIKit store 与 nim sdk 实例 + const { store, nim } = useStateContext() + + const conversationId = store.uiStore.selectedConversation + const conversationType = nim.V2NIMConversationIdUtil.parseConversationType( + store.uiStore.selectedConversation + ) + const receiverId = nim.V2NIMConversationIdUtil.parseConversationTargetId( + store.uiStore.selectedConversation + ) const messageActionDropdownContainerRef = useRef<HTMLDivElement>(null) @@ -113,11 +115,8 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { if (callViewProviderRef.current?.neCall) { //注册呼叫结束事件监听 callViewProviderRef.current?.neCall?.on('onRecordSend', (options) => { - const sessionId = store.uiStore.selectedSession - // @ts-ignore 消息列表增加话单消息 - store.msgStore.addMsg(sessionId, [options]) - // @ts-ignore 使增加的消息出现在视野可见区域 - document.getElementById(options.idClient)?.scrollIntoView() + store.msgStore.addMsg(options.conversationId, [options]) + document.getElementById(options.messageClientId)?.scrollIntoView() }) // 设置呼叫超时时间 callViewProviderRef.current?.neCall?.setTimeout(30) @@ -128,13 +127,16 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { pauseAllVideo() }) } - }, [callViewProviderRef.current?.neCall]) + }, [callViewProviderRef.current?.neCall, store.msgStore]) // 发起呼叫 const handleCall = useCallback( async (callType) => { try { - await callViewProviderRef.current?.call?.({ accId: to, callType }) + await callViewProviderRef.current?.call?.({ + accId: receiverId, + callType, + }) setCallingVisible(false) } catch (error) { switch (error.code) { @@ -153,7 +155,7 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { } } }, - [to] + [receiverId] ) // 重新渲染发送按钮,增加呼叫按钮 @@ -173,7 +175,9 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { }, { action: 'calling', - visible: scene === 'team' || sdkVersion === 2 ? false : true, + visible: + conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P, render: () => { return ( <Button type="text" disabled={false}> @@ -194,26 +198,29 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { visible: true, }, ], - [handleCall, callingVisible, scene, sdkVersion] + [callingVisible, handleCall, conversationType] ) // 根据msg.type 自定义渲染话单消息,当msg.type 为 g2 代表的是话单消息,使用renderP2pCustomMessage进行自定义渲染 const renderP2pCustomMessage = useCallback( - (msg) => { - msg = msg.msg + (options: RenderP2pCustomMessageOptions) => { + const msg = options.msg // msg.type 为 g2 代表的是话单消息 renderP2pCustomMessage 返回 null 就会按照组件默认的逻辑进行展示消息 - if (msg.type !== 'g2' || sdkVersion == 2) { + if ( + msg.messageType !== V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL + ) { return null } - const { attach } = msg - const duration = attach?.durations[0]?.duration - const status = attach?.status - const type = attach?.type + const attach = msg.attachment as any + const raw = JSON.parse(attach?.raw || '{}') + const duration = raw.durations[0]?.duration + const status = raw.status + const type = raw.type const icon = type == 1 ? 'icon-yuyin8' : 'icon-shipin8' - const myAccount = store.userStore.myUserInfo.account + const myAccount = store.userStore.myUserInfo.accountId //判断当前消息是发出的消息还是接收的消息 - const isSelf = msg.from === myAccount - const account = isSelf ? myAccount : to + const isSelf = msg.senderId === myAccount + const account = isSelf ? myAccount : receiverId const deleteG2Message = (msg) => { console.log('msg:', msg) store.msgStore.deleteMsgActive([msg]) @@ -222,14 +229,13 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { <div className={classNames('wrapper', { 'wrapper-self': isSelf })}> <ComplexAvatarContainer account={account} /> <Dropdown - key={msg.idClient} + key={msg.messageClientId} trigger={['contextMenu']} getPopupContainer={(triggerNode) => messageActionDropdownContainerRef.current || triggerNode } overlay={ <Menu - // @ts-ignore onClick={() => deleteG2Message(msg)} items={[ { @@ -257,13 +263,13 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { > <i className={classNames('iconfont', 'g2-icon', icon)}></i> <span>{g2StatusMap[status]}</span> - {duration && ( + {duration ? ( <span className="g2-time"> {convertSecondsToTime(duration)} </span> - )} + ) : null} </div> - <div className="time">{renderMsgDate(msg.time)}</div> + <div className="time">{renderMsgDate(msg.createTime)}</div> </div> </Dropdown> </div> @@ -271,10 +277,10 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { }, [ handleCall, - sdkVersion, - to, + receiverId, store.uiStore, - store.userStore.myUserInfo.account, + store.msgStore, + store.userStore.myUserInfo.accountId, ] ) @@ -305,7 +311,7 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { <div className="icon-label">{t('session')}</div> </div> </Badge> - <Badge dot={!!store.sysMsgStore.unreadSysMsgCount}> + <Badge dot={!!store.sysMsgStore.getTotalUnreadMsgsCount()}> <div className={classNames('contact-icon', { active: model === 'contact', @@ -356,12 +362,17 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { <ContactInfoContainer afterSendMsgClick={() => setModel('chat')} onGroupItemClick={() => setModel('chat')} - afterAcceptApplyFriend={(account) => { + afterAcceptApplyFriend={(data) => { + const textMsg = nim.V2NIMMessageCreator.createTextMessage( + t('passFriendAskText') + ) store.msgStore - .sendTextMsgActive({ - scene: 'p2p', - to: account, - body: t('passFriendAskText'), + .sendMessageActive({ + msg: textMsg, + conversationId: + nim.V2NIMConversationIdUtil.p2pConversationId( + data.operatorAccountId + ), }) .then(() => { setModel('chat') @@ -384,17 +395,20 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { onLogout, p2pMsgReceiptVisible, teamMsgReceiptVisible, - sdkVersion, renderP2pCustomMessage, - store.sysMsgStore.unreadSysMsgCount, + store.sysMsgStore, + store.msgStore, + nim.V2NIMMessageCreator, + nim.V2NIMConversationIdUtil, + needMention, + teamManagerVisible, ]) - // IM elite(IM 2) sdk 没有信令, 无法初始化呼叫组件 - return sdkVersion === 1 ? ( + + return ( <CallViewProvider ref={callViewProviderRef} neCallConfig={{ - // @ts-ignore - nim: nim.nim, + nim, appkey, debug: true, }} @@ -405,8 +419,6 @@ const IMApp: React.FC<IMAppProps> = observer((props) => { > {renderContent()} </CallViewProvider> - ) : ( - renderContent() ) }) @@ -414,9 +426,6 @@ const IMAppContainer: React.FC<IMContainerProps> = (props) => { const { appkey, account, token, onLogout } = props // 国际化语言类型 const [curLanguage, setCurLanguage] = useState<'zh' | 'en'>('zh') - // sdk版本 - const urlParams = new URLSearchParams(window.location.search) - const sdkVersion = (Number(urlParams.get('sdkVersion')) as 1 | 2) || 1 // 添加好友是否需要验证 const [addFriendNeedVerify, setAddFriendNeedVerify] = useState<boolean>(true) //单聊消息是否显示已读未读 @@ -429,38 +438,35 @@ const IMAppContainer: React.FC<IMContainerProps> = (props) => { const [needMention, setNeedMention] = useState<boolean>(true) // 是否开启群管理员功能 const [teamManagerVisible, setTeamManagerVisible] = useState<boolean>(true) - const languageMap = { zh, en } - // 初始化参数 - const initOptions = useMemo(() => { + const languageMap = useMemo(() => ({ zh, en }), []) + // 本地默认行为参数 + const localOptions: Partial<LocalOptions> = useMemo(() => { return { - appkey, - account, - token, - lbsUrls: ['https://lbs.netease.im/lbs/webconf.jsp'], - linkUrl: 'weblink.netease.im', - needReconnect: true, - reconnectionAttempts: 5, + // 添加好友模式,默认需要验证 + addFriendNeedVerify, + // 群组被邀请模式,默认不需要验证 + teamAgreeMode: + V2NIMConst.V2NIMTeamAgreeMode.V2NIM_TEAM_AGREE_MODE_NO_AUTH, + // 单聊消息是否显示已读未读 + p2pMsgReceiptVisible, + // 群聊消息是否显示已读未读 + teamMsgReceiptVisible, + // 是否需要@消息 + needMention, + // 是否开启群管理员 + teamManagerVisible, + // 是否显示在线离线状态 + loginStateVisible: false, + // 是否允许转让群主 + allowTransferTeamOwner: true, } - }, [appkey, account, token]) - // 本地默认行为参数 - const localOptions: Partial<LocalOptions> = { - // 添加好友模式,默认需要验证 + }, [ addFriendNeedVerify, - // 群组被邀请模式,默认不需要验证 - teamBeInviteMode: 'noVerify', - // 单聊消息是否显示已读未读 - p2pMsgReceiptVisible, - // 群聊消息是否显示已读未读 - teamMsgReceiptVisible, - // 是否需要@消息 needMention, - // 是否开启群管理员 + p2pMsgReceiptVisible, teamManagerVisible, - // 是否显示在线离线状态 - loginStateVisible: true, - // 是否允许转让群主 - allowTransferTeamOwner: true, - } + teamMsgReceiptVisible, + ]) const changeLanguage = useCallback((value: 'zh' | 'en') => { setCurLanguage(value) @@ -494,15 +500,37 @@ const IMAppContainer: React.FC<IMContainerProps> = (props) => { setTeamManagerVisible(_teamManagerVisible === 'true') } }, []) + + const nim = useMemo(() => { + const nim = V2NIM.getInstance({ + appkey, + account, + token, + debugLevel: 'debug', + apiVersion: 'v2', + }) + return nim + }, [account, token, appkey]) + + useEffect(() => { + nim.V2NIMLoginService.login(account, token, { + retryCount: 5, + }) + + return () => { + nim.V2NIMLoginService.logout() + } + }, [nim, account, token]) + return ( <ConfigProvider locale={curLanguage === 'zh' ? zhCN : enUS}> <div className="im-app-example"> <div className="container"> <Provider - sdkVersion={sdkVersion} localeConfig={languageMap[curLanguage]} - initOptions={initOptions} localOptions={localOptions} + nim={nim} + singleton={true} > <IMApp onLogout={onLogout} @@ -511,7 +539,6 @@ const IMAppContainer: React.FC<IMContainerProps> = (props) => { locale={curLanguage} changeLanguage={changeLanguage} addFriendNeedVerify={addFriendNeedVerify} - sdkVersion={sdkVersion} p2pMsgReceiptVisible={p2pMsgReceiptVisible} teamMsgReceiptVisible={teamMsgReceiptVisible} needMention={needMention} diff --git a/vue/package.json b/vue/package.json index 1f5148f..3e4c6ad 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.8.0", + "@xkit-yx/im-kit-ui": "^10.x", "react": "^16.8.0", "react-dom": "^16.8.0", "vue": "^3.2.45" diff --git a/vue/src/App.vue b/vue/src/App.vue index 97e980f..91d64d2 100644 --- a/vue/src/App.vue +++ b/vue/src/App.vue @@ -4,6 +4,7 @@ <script lang="ts"> import IMApp from "./components/IMApp/index.vue"; import { IMUIKit } from "@xkit-yx/im-kit-ui"; +import V2NIM, { V2NIMConst } from 'nim-web-sdk-ng' import { app } from "./main"; export default { name: "App", @@ -25,7 +26,7 @@ export default { // 添加好友模式,默认需要验证 addFriendNeedVerify: true, // 群组被邀请模式,默认不需要验证 - teamBeInviteMode: 'noVerify' as "noVerify" | "needVerify", + teamAgreeMode: V2NIMConst.V2NIMTeamAgreeMode.V2NIM_TEAM_AGREE_MODE_NO_AUTH, // 单聊消息是否显示已读未读 默认 false p2pMsgReceiptVisible: true, // 群聊消息是否显示已读未读 默认 false @@ -37,10 +38,25 @@ export default { // 是否允许转让群主 allowTransferTeamOwner: true, } + + // 初始化 IM SDK 实例 + const nim = V2NIM.getInstance({ + appkey: initOptions.appkey, + account: initOptions.account, + token: initOptions.token, + debugLevel: 'debug', + apiVersion: 'v2', + }) + + // IM 连接 + nim.V2NIMLoginService.login(initOptions.account, initOptions.token, { + retryCount: 5, + }) + + // 初始化 UIKit 实例 app.config.globalProperties.$uikit = new IMUIKit({ - initOptions, + nim, singleton: true, - sdkVersion: 1, localOptions }); if (app.config.globalProperties.$uikit) {