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 07abce2..bed1e7e 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/Container.tsx @@ -14,10 +14,11 @@ import { SettingActionItemProps } from './components/ChatActionBar' import { V2NIMConversationType, V2NIMConversation, -} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +} from 'nim-web-sdk-ng/dist/esm/nim/src/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' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' +import { MAX_UPLOAD_FILE_SIZE } from '../constant' export interface ActionRenderProps extends ChatMessageInputProps { conversationType: V2NIMConversationType @@ -28,7 +29,7 @@ export interface Action { /** 按钮类型 */ - action: 'emoji' | 'sendImg' | 'sendFile' | string + action: 'emoji' | 'sendImg' | 'sendFile' | 'aiTranslate' | 'sendMsg' | string /** 是否显示该按钮,自带按钮默认 true,新增自定义按钮默认 false */ @@ -152,6 +153,22 @@ export interface ChatContainerProps { 公共样式前缀 */ commonPrefix?: string + /** + 消息可撤回的最大时间,单位毫秒,默认 2 * 60 * 1000, 最大支持7天内的消息可撤回 + */ + msgRecallTime?: number + /** + 是否展示陌生人提示 + */ + strangerTipVisible?: boolean + /** + 上拉加载消息滚动模式,组件在上拉加载消息时,默认会自动滚动到最新的消息,当组件位于可滚动的页面中时,可能会造成滚动异常,设置nearest即可 + */ + scrollIntoMode?: 'nearest' + /** + * 最大上传文件大小,单位Mb,默认 100M + */ + maxUploadFileSize?: number } export const ChatContainer: React.FC = observer( @@ -161,6 +178,7 @@ export const ChatContainer: React.FC = observer( p2pSettingActions, teamSettingActions, msgOperMenu, + maxUploadFileSize = MAX_UPLOAD_FILE_SIZE, onSendText, afterTransferTeam, renderEmpty, @@ -175,8 +193,11 @@ export const ChatContainer: React.FC = observer( renderMessageInnerContent, renderMessageOuterContent, + msgRecallTime = 2 * 60 * 1000, prefix = 'chat', commonPrefix = 'common', + strangerTipVisible = true, + scrollIntoMode, }) => { const { store, nim } = useStateContext() @@ -205,8 +226,10 @@ export const ChatContainer: React.FC = observer( commonPrefix={commonPrefix} onSendText={onSendText} actions={actions} + maxUploadFileSize={maxUploadFileSize} conversationType={conversationType} receiverId={receiverId} + msgRecallTime={msgRecallTime} settingActions={p2pSettingActions} msgOperMenu={msgOperMenu} renderP2pCustomMessage={renderP2pCustomMessage} @@ -216,16 +239,20 @@ export const ChatContainer: React.FC = observer( renderMessageName={renderMessageName} renderMessageInnerContent={renderMessageInnerContent} renderMessageOuterContent={renderMessageOuterContent} + strangerTipVisible={strangerTipVisible} + scrollIntoMode={scrollIntoMode} /> ) : conversationType === V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM ? ( = observer( renderMessageName={renderMessageName} renderMessageInnerContent={renderMessageInnerContent} renderMessageOuterContent={renderMessageOuterContent} + scrollIntoMode={scrollIntoMode} /> ) : null ) : renderEmpty ? ( diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAISearch/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAISearch/index.tsx index 77feb85..49a146e 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAISearch/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAISearch/index.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react' import React, { FC, useState } from 'react' import { useStateContext, useTranslation } from '../../../common' import { LoadingOutlined } from '@ant-design/icons' -import { V2NIMAIModelRoleType } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMAIService' +import { V2NIMAIModelRoleType } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMAIService' import { getAIErrorMap } from '../../../utils' export interface ChatAISearchProps { @@ -97,7 +97,7 @@ export const ChatAISearch: FC = observer( bordered={true} className={`${_prefix}-textarea`} value={inputValue} - onInput={onInputChangeHandler} + onChange={onInputChangeHandler} onPressEnter={onPressEnterHandler} autoSize={{ maxRows: 3 }} /> diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAITranslate/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAITranslate/index.tsx index 1929693..d65ebc5 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAITranslate/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatAITranslate/index.tsx @@ -8,7 +8,7 @@ import { LoadingOutlined, } from '@ant-design/icons' import { getAIErrorMap, logger } from '../../../utils' -import { V2NIMError } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/types' +import { V2NIMError } from 'nim-web-sdk-ng/dist/esm/nim/src/types' export interface ChatAITranslateProps { inputValue: string 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 0448404..c334e8f 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 @@ -26,7 +26,6 @@ const ChatAddMemebers: React.FC = ({ }) => { const [selectedAccounts, setSelectedAccounts] = useState(defaultAccounts) - // const [searchText, setSearchText] = useState('') useEffect(() => { resetState() @@ -52,9 +51,22 @@ const ChatAddMemebers: React.FC = ({ setSelectedAccounts(defaultAccounts) } - const handleSelect = useCallback((selectedList: string[]) => { - setSelectedAccounts(selectedList) - }, []) + const handleSelect = useCallback( + (selectedList: string[]) => { + // 去除已入群的成员 + function removeCommonElements(arr1, arr2) { + return arr2.filter((item) => !arr1.includes(item)) + } + + const resultSelectedList = removeCommonElements( + defaultAccounts, + selectedList + ) + + setSelectedAccounts(resultSelectedList) + }, + [defaultAccounts] + ) return ( = ({ destroyOnClose={true} okText={t('addTeamMemberText')} > - {/* */}
= ({ const defaultMenus: MenuItem[] = [ { show: - msg.messageType === + msg?.messageType === V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO ? 0 : 1, @@ -89,7 +89,7 @@ const CollectionItem: React.FC = ({ : defaultMenus return finalMenus.filter((item) => item.show) - }, [menus, t, msg.messageType]) + }, [menus, t, msg?.messageType]) const handleMenuClick = ({ key }: { key: string }) => { onMenuClick({ @@ -108,7 +108,7 @@ const CollectionItem: React.FC = ({ overlayClassName={`${_prefix}-dropdown`} >
- + {msg && }
{collectionData.senderName} diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCollectionList/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCollectionList/index.tsx index d9fba87..b20f2a5 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCollectionList/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCollectionList/index.tsx @@ -6,9 +6,9 @@ import { V2NIMCollection, V2NIMCollectionOption, V2NIMMessage, -} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMMessageService' +} from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMMessageService' import { logger } from '../../../utils' -import { V2NIMError } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/types' +import { V2NIMError } from 'nim-web-sdk-ng/dist/esm/nim/src/types' import { MenuItem } from '../ChatMessageItem' import { debounce } from '@xkit-yx/utils' import ChatForwardModal from '../ChatForwardModal' 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 deleted file mode 100644 index 00f6b95..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/index.tsx +++ /dev/null @@ -1,134 +0,0 @@ -import React, { useState, useEffect } from 'react' -import { Form, Modal, Input } from 'antd' -import { - urls, - GroupAvatarSelect, - FriendSelectContainer, - useTranslation, -} from '../../../common' - -export interface GroupCreateFormParams { - name: string -} - -// TODO 抽到 common 中,跟 search-kit 复用 -export interface GroupCreateProps { - defaultAccounts?: string[] - visible: boolean - onCancel: () => void - onGroupCreate: ( - params: GroupCreateFormParams & { - selectedAccounts: string[] - avatar: string - } - ) => void - - prefix?: string - commonPrefix?: string -} - -const emptyArr = [] - -const GroupCreate: React.FC = ({ - defaultAccounts = emptyArr, - onGroupCreate, - visible, - onCancel, - - prefix = 'chat', - commonPrefix = 'common', -}) => { - const { t } = useTranslation() - const [form] = Form.useForm() - const _prefix = `${prefix}-group-create` - - const [avatar, setAvatar] = useState(urls[Math.floor(Math.random() * 5)]) - const [selectedAccounts, setSelectedAccounts] = useState([]) - - useEffect(() => { - setSelectedAccounts([...new Set(defaultAccounts)]) - }, [defaultAccounts]) - - const handleOk = () => { - form - .validateFields() - .then((values) => { - onGroupCreate({ - ...values, - selectedAccounts, - avatar, - name: values.name.trim(), - }) - resetState() - }) - .catch(() => { - // - }) - } - - const handleCancel = () => { - resetState() - onCancel() - } - - const resetState = () => { - form.resetFields() - setAvatar(urls[Math.floor(Math.random() * 5)]) - setSelectedAccounts([]) - } - - return ( - -
- - { - form.setFieldsValue({ - name: e.target.value, - }) - }} - autoComplete="off" - /> - - - - -
- setSelectedAccounts(accounts)} - selectedAccounts={selectedAccounts} - /> -
-
-
- ) -} - -export default GroupCreate diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/style/index.less b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/style/index.less deleted file mode 100644 index c848ca6..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/style/index.less +++ /dev/null @@ -1,3 +0,0 @@ -@import '../../../style/theme.less'; - -@prefix-cls: ~'@{chat-prefix}-group-create'; diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/style/index.ts b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/style/index.ts deleted file mode 100644 index 06533e4..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/style/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -import 'antd/lib/form/style' -import 'antd/lib/modal/style' -import 'antd/lib/input/style' -import './index.less' 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 55f781a..66c7fa8 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 @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react' +import React, { useCallback, useEffect, useState } from 'react' import { CommonIcon, ComplexAvatarContainer, @@ -8,8 +8,9 @@ import { import classNames from 'classnames' import { observer } from 'mobx-react' import { storeConstants } from '@xkit-yx/im-store-v2' -import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' -import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMAIService' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' +import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMAIService' +import { AutoSizer, List } from 'react-virtualized' export type MentionedMember = { account: string; appellation: string } @@ -26,7 +27,7 @@ export const ChatAtMemberList: React.FC = observer( allowAtAll = true, prefix = 'chat', commonPrefix = 'common', - mentionMembers, + mentionMembers = [], onSelect, }) => { const _prefix = `${prefix}-at-member` @@ -83,6 +84,48 @@ export const ChatAtMemberList: React.FC = observer( } }, [activeIndex, mentionMembers, onSelect, t, store.uiStore]) + const rowRenderer = useCallback( + ({ index, key, style }) => { + const member = mentionMembers[index] + + return ( +
+
{ + onSelect?.({ + account: member.accountId, + appellation: store.uiStore.getAppellation({ + account: member.accountId, + teamId: (member as V2NIMTeamMember).teamId, + ignoreAlias: true, + }), + }) + }} + onMouseEnter={() => setActiveIndex(index)} + > + + + {store.uiStore.getAppellation({ + account: member.accountId, + teamId: (member as V2NIMTeamMember).teamId, + })} + +
+
+ ) + }, + [mentionMembers, prefix, commonPrefix] + ) + return (
{allowAtAll && ( @@ -104,38 +147,18 @@ export const ChatAtMemberList: React.FC = observer( {t('teamAll')}
)} - {mentionMembers?.map((member, index) => ( -
{ - onSelect?.({ - account: member.accountId, - appellation: store.uiStore.getAppellation({ - account: member.accountId, - teamId: (member as V2NIMTeamMember).teamId, - ignoreAlias: true, - }), - }) - }} - onMouseEnter={() => setActiveIndex(index)} - > - + {({ height, width }) => ( + - - {store.uiStore.getAppellation({ - account: member.accountId, - teamId: (member as V2NIMTeamMember).teamId, - })} - -
- ))} + )} +
) } 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 a885657..49ca004 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 @@ -17,7 +17,6 @@ import { useStateContext, } from '../../../common' import { Action } from '../../Container' -import { MAX_UPLOAD_FILE_SIZE } from '../../../constant' import { storeConstants } from '@xkit-yx/im-store-v2' import { CloseOutlined } from '@ant-design/icons' import { observer } from 'mobx-react' @@ -29,10 +28,11 @@ import { YxAitMsg, YxServerExt, } 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' -import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMAIService' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' +import { V2NIMConversationType } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMConversationService' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' +import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMAIService' +import { useImgPaste } from './useImgPaste' const { TextArea } = Input @@ -49,6 +49,7 @@ export interface ChatMessageInputProps { allowAtAll?: boolean inputValue?: string translateOpen: boolean + maxUploadFileSize: number setInputValue: (value: string) => void onTranslate: (open: boolean) => void onSendText: (value: string, ext?: YxServerExt) => void @@ -81,6 +82,7 @@ const ChatMessageInput = observer( inputValue = '', translateOpen, replyMsg, + maxUploadFileSize, setInputValue, onTranslate, onSendText, @@ -143,6 +145,7 @@ const ChatMessageInput = observer( arrow={false} overlay={ - - - {t('imgText')} - +
+ + + {t('imgText')} + +
), }, @@ -178,7 +183,7 @@ const ChatMessageInput = observer( className={`${_prefix}-icon-image`} type="icon-shipin8" /> - + {t('videoText')} @@ -264,10 +269,12 @@ const ChatMessageInput = observer( }, ] + // 消息右键菜单 const finalActions = actions ? mergeActions(defaultActions, actions, 'action') : defaultActions + // @成员处理 const filterAtMembers = useMemo(() => { if (atMemberSearchText) { const res = mentionMembers?.filter((member) => { @@ -285,23 +292,6 @@ const ChatMessageInput = observer( } }, [mentionMembers, atMemberSearchText, store.uiStore]) - useEffect(() => { - setAtMemberSearchText('') - setAtVisible(false) - }, [receiverId]) - - useEffect(() => { - if (atMemberSearchText) { - if (filterAtMembers?.length) { - setAtVisible(true) - } else { - setAtVisible(false) - } - } else { - setAtVisible(false) - } - }, [filterAtMembers, atMemberSearchText]) - const onAtMembersExtHandler = () => { let ext: YxServerExt | void = void 0 @@ -360,6 +350,7 @@ const ChatMessageInput = observer( return ext as unknown as YxServerExt } + // 用户处理input中的@成员昵称 const onTextAreaSelectionRangeHandler = () => { function getCursorPosition( cursorIndex: number @@ -455,6 +446,7 @@ const ChatMessageInput = observer( [atMemberSearchText, selectedAtMembers, setInputValue, atVisible] ) + // 点击发送消息 const onClickSendMsgHandler = (e) => { if (atVisible) { e.preventDefault() @@ -475,6 +467,7 @@ const ChatMessageInput = observer( setSelectedAtMembers([]) } + // 回车发送 const onPressEnterHandler = ( e: React.KeyboardEvent ) => { @@ -498,6 +491,7 @@ const ChatMessageInput = observer( } } + // 键盘相关事件处理 @相关 const onKeyDownHandler = (e: React.KeyboardEvent) => { if (['ArrowLeft', 'ArrowRight'].includes(e.key)) { setAtVisible(false) @@ -541,16 +535,15 @@ const ChatMessageInput = observer( const onClickHandler = onTextAreaSelectionRangeHandler + // 文件上传前处理 const onBeforeUploadFileHandler = ( file: File ): boolean | Promise => { - const isLimit = file.size / 1024 / 1000 > MAX_UPLOAD_FILE_SIZE + const isLimit = file.size / 1024 / 1000 > maxUploadFileSize if (isLimit) { message.error( - `${t('uploadLimitText')}${MAX_UPLOAD_FILE_SIZE}${t( - 'uploadLimitUnit' - )}` + `${t('uploadLimitText')}${maxUploadFileSize}${t('uploadLimitUnit')}` ) } else { onSendFile(file) @@ -559,16 +552,15 @@ const ChatMessageInput = observer( return false } + // 图片上传前处理 const onBeforeUploadImgHandler = ( file: File ): boolean | Promise => { - const isLimit = file.size / 1024 / 1000 > MAX_UPLOAD_FILE_SIZE + const isLimit = file.size / 1024 / 1000 > maxUploadFileSize if (isLimit) { message.error( - `${t('uploadLimitText')}${MAX_UPLOAD_FILE_SIZE}${t( - 'uploadLimitUnit' - )}` + `${t('uploadLimitText')}${maxUploadFileSize}${t('uploadLimitUnit')}` ) } else { onSendImg(file) @@ -577,16 +569,15 @@ const ChatMessageInput = observer( return false } + // 视频上传前处理 const onBeforeUploadVideoHandler = ( file: File ): boolean | Promise => { - const isLimit = file.size / 1024 / 1000 > MAX_UPLOAD_FILE_SIZE + const isLimit = file.size / 1024 / 1000 > maxUploadFileSize if (isLimit) { message.error( - `${t('uploadLimitText')}${MAX_UPLOAD_FILE_SIZE}${t( - 'uploadLimitUnit' - )}` + `${t('uploadLimitText')}${maxUploadFileSize}${t('uploadLimitUnit')}` ) } else { onSendVideo(file) @@ -595,13 +586,7 @@ const ChatMessageInput = observer( return false } - // const onUploadImgHandler = (file: any): any => { - // onSendImg(file) - // } - // const onUploadFileHandler = (file: any): any => { - // onSendFile(file) - // } - + // 点击表情 const onEmojiClickHandler = (tag: string) => { const input = textAreaRef.current?.resizableTextArea?.textArea @@ -634,6 +619,7 @@ const ChatMessageInput = observer( ) + // 键盘上方的回复消息 const replyMsgContent = () => { if (replyMsg) { const nick = store.uiStore.getAppellation({ @@ -654,6 +640,43 @@ const ChatMessageInput = observer( } } + // 粘贴图片发送 + const { handleImgPaste } = useImgPaste({ onSendImg }) + + useEffect(() => { + if (textAreaRef.current) { + textAreaRef?.current?.focus() + textAreaRef.current.resizableTextArea?.textArea.addEventListener( + 'paste', + handleImgPaste + ) + } + + return () => { + textAreaRef.current?.resizableTextArea?.textArea.removeEventListener( + 'paste', + handleImgPaste + ) + } + }, [textAreaRef, store.uiStore.selectedConversation]) + + useEffect(() => { + setAtMemberSearchText('') + setAtVisible(false) + }, [receiverId]) + + useEffect(() => { + if (atMemberSearchText) { + if (filterAtMembers?.length) { + setAtVisible(true) + } else { + setAtVisible(false) + } + } else { + setAtVisible(false) + } + }, [filterAtMembers, atMemberSearchText]) + useImperativeHandle( ref, () => ({ @@ -708,11 +731,13 @@ const ChatMessageInput = observer( placeholder={placeholder} value={inputValue} disabled={mute} - onInput={onInputChangeHandler} + // antd 问题 onInput会出现在火狐浏览器上无法输入中文 此处改为onchange + //onInput={onInputChangeHandler} + onChange={onInputChangeHandler} onPressEnter={onPressEnterHandler} onKeyDown={onKeyDownHandler} onClick={onClickHandler} - autoSize={{ maxRows: 2 }} + autoSize={{ maxRows: 3 }} />
{finalActions.map((item) => diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/style/chatAtMemberList.less b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/style/chatAtMemberList.less index d070c45..3739714 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/style/chatAtMemberList.less +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/style/chatAtMemberList.less @@ -5,7 +5,8 @@ .@{prefix-cls}-wrap { padding: 10px 0; - max-height: 160px; + min-height: 200px; + max-height: 200px; overflow-y: auto; width: 100%; .@{prefix-cls}-item { diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/style/index.less b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/style/index.less index df99d3a..4911903 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/style/index.less @@ -96,7 +96,6 @@ display: flex; justify-content: flex-end; align-items: center; - .@{ant-prefix}-btn { padding: 0; padding: 0; 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 25ced09..c8f74b0 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 @@ -5,6 +5,7 @@ import { ExclamationCircleFilled, RollbackOutlined, DeleteOutlined, + SwapOutlined, } from '@ant-design/icons' import classNames from 'classnames' import { @@ -22,7 +23,7 @@ import { V2NIMMessageForUI, YxTopMessage, } from '@xkit-yx/im-store-v2/dist/types/types' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export type MenuItemKey = | 'recall' @@ -32,6 +33,7 @@ export type MenuItemKey = | 'forward' | 'top' | 'unTop' + | 'voiceToText' | string export type AvatarMenuItem = 'mention' @@ -48,9 +50,12 @@ export interface MenuItem { export interface MessageItemProps { msg: V2NIMMessageForUI + /** 置顶消息 */ topMessage?: YxTopMessage + /** 回复消息 */ replyMsg?: V2NIMMessageForUI normalStatusRenderer?: React.ReactNode + /** 消息右键操作菜单 */ msgOperMenu?: MsgOperMenuItem[] onReeditClick: (msg: V2NIMMessageForUI) => void onResend: (msg: V2NIMMessageForUI) => void @@ -104,9 +109,9 @@ export const ChatMessageItem: React.FC = observer( conversationType, isSelf, recallType = '', - canRecall = false, errorCode, messageStatus, + textOfVoice, } = msg const messageActionDropdownContainerRef = useRef(null) @@ -114,6 +119,7 @@ export const ChatMessageItem: React.FC = observer( const aiErrorMap = getAIErrorMap(t) + // 优先级按照 备注 > 群昵称 > 好友昵称 > 消息上的昵称 > 好友账号 const nick = store.uiStore.getAppellation({ account: senderId, teamId: @@ -133,11 +139,13 @@ export const ChatMessageItem: React.FC = observer( ignoreAlias: true, }) + // 重发消息 const handleResendMsg = () => { onResend(msg) } - const renderSendStatus = () => { + // 消息发送状态 + const renderSendMsgStatus = () => { if ( sendingState === V2NIMConst.V2NIMMessageSendingState.V2NIM_MESSAGE_SENDING_STATE_SENDING @@ -169,13 +177,9 @@ export const ChatMessageItem: React.FC = observer( return normalStatusRenderer || null } + // 右键菜单列表 const renderMenuItems = () => { const defaultMenuItems: MenuItem[] = [ - // { - // label: '复制', - // key: 'copy', - // icon: , - // }, { show: [ V2NIMConst.V2NIMMessageSendingState @@ -190,7 +194,7 @@ export const ChatMessageItem: React.FC = observer( icon: , }, { - show: uploadProgress === void 0 ? 1 : 0, + show: 1, label: t('deleteText'), key: 'delete', icon: , @@ -266,11 +270,32 @@ export const ChatMessageItem: React.FC = observer( icon: , }, { - show: canRecall ? 1 : 0, + show: msg.isSelf + ? [ + V2NIMConst.V2NIMMessageSendingState + .V2NIM_MESSAGE_SENDING_STATE_SENDING, + V2NIMConst.V2NIMMessageSendingState + .V2NIM_MESSAGE_SENDING_STATE_FAILED, + ].includes(sendingState) + ? 0 + : 1 + : 0, label: t('recallText'), key: 'recall', icon: , }, + { + // 是语音消息则可以有转文字的功能. + // 也因为 web 端无法发送语音消息, 故而也不用做其他的判断 + show: + !textOfVoice && + messageType === V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO + ? 1 + : 0, + label: t('voiceToText'), + key: 'voiceToText', + icon: , + }, ] const menuItems = msgOperMenu ? mergeActions(defaultMenuItems, msgOperMenu, 'key') @@ -370,11 +395,14 @@ export const ChatMessageItem: React.FC = observer( )}
{isSelf && ( -
{renderSendStatus()}
+
+ {renderSendMsgStatus()} +
)} {renderMessageOuterContent?.(msg) ?? (
{renderMessageInnerContent?.(msg) ?? ( + // 消息体的主要渲染逻辑都在这个ParseSession里 void onScroll?: (e: React.UIEvent) => void + /** + 是否展示陌生人提示 + */ + strangerTipVisible?: boolean } const ChatP2pMessageList = observer( @@ -44,6 +48,7 @@ const ChatP2pMessageList = observer( onReceiveMsgBtnClick, loadingMore, noMore, + strangerTipVisible = true, onResend, onMessageAction, onReeditClick, @@ -94,6 +99,7 @@ const ChatP2pMessageList = observer( unread={msg.createTime <= msgReceiptTime ? 0 : 1} read={msg.createTime <= msgReceiptTime ? 1 : 0} prefix={commonPrefix} + size={16} /> ) : null } @@ -123,7 +129,7 @@ const ChatP2pMessageList = observer(
) : null} - {relation === 'stranger' ? ( + {relation === 'stranger' && strangerTipVisible ? ( ) : null } diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMessageList/style/index.less b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMessageList/style/index.less index eecc00b..37ccb7d 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMessageList/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMessageList/style/index.less @@ -49,4 +49,4 @@ top: 60px; left: 0; } -} +} \ No newline at end of file diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupActionModal.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupActionModal.tsx deleted file mode 100644 index aa146dd..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupActionModal.tsx +++ /dev/null @@ -1,167 +0,0 @@ -import React, { useState } from 'react' -import { Modal, Radio, Input, message, Button } from 'antd' -import { - ComplexAvatarContainer, - 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 { SearchOutlined, CloseOutlined } from '@ant-design/icons' -interface GroupActionModalProps { - title: string // 标题 - visible: boolean - members: (TeamMember & Partial)[] // 成员列表 - onOk: (account: string) => void // 确认操作的回调函数 - onCancel: () => void - prefix?: string - commonPrefix?: string - teamId: string - groupSearchText: string - onTeamMemberSearchChange: (searchText: string) => void -} - -const GroupActionModal: React.FC = ({ - title, - members, - onOk, - visible, - onCancel, - teamId, - onTeamMemberSearchChange, - groupSearchText, - prefix = 'chat', - commonPrefix = 'common', -}) => { - const [selectedMemberId, setSelectedMemberId] = useState('') // 选中的成员 ID - - const handleMemberSelect = (e) => { - setSelectedMemberId(e.target.value) - } - const { t } = useTranslation() - - const { store } = useStateContext() - const _prefix = `${prefix}-group-action-modal` - - const handleCancel = () => { - onCancel() - setSelectedMemberId('') - } - - const handleOk = () => { - try { - onOk(selectedMemberId) - } catch (error) { - message.error(t('transferTeamFailedText')) - } - } - - const handleSearch = (searchText: string) => { - onTeamMemberSearchChange(searchText) - } - - const handleSelectReset = () => { - setSelectedMemberId('') - } - return ( - -
-
- } - allowClear - value={groupSearchText} - className={`${_prefix}-content-input`} - placeholder={t('searchTeamMemberPlaceholder')} - onChange={(e) => handleSearch(e.target.value)} - /> - {members.length ? ( -
-
- {t('teamMemberText')} -
- - {members.map((member) => { - return ( -
- - - - {store.uiStore.getAppellation({ - account: member.account, - teamId: member.teamId, - })} - -
- ) - })} -
-
- ) : ( -
- {t('searchNoResText')} -
- )} -
-
-
- {t('selectedText')} - {selectedMemberId ? 1 : 0}/1 -
- {selectedMemberId ? ( -
- - - {store.uiStore.getAppellation({ - account: selectedMemberId, - teamId: teamId, - })} - - -
- ) : null} -
-
-
- ) -} - -export default GroupActionModal 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 4f3ee20..5218a19 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 @@ -3,13 +3,13 @@ import { Form, Input, Button } from 'antd' import { GroupAvatarSelect, useTranslation, CrudeAvatar } from '../../../common' import { V2NIMTeam, - V2NIMUpdatedTeamInfo, -} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' + V2NIMUpdateTeamInfoParams, +} from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' export interface GroupDetailmProps { team: V2NIMTeam hasPower: boolean - onUpdateTeamInfo: (team: V2NIMUpdatedTeamInfo) => void + onUpdateTeamInfo: (team: V2NIMUpdateTeamInfoParams) => void prefix?: string commonPrefix?: string @@ -46,7 +46,7 @@ const GroupDetail: FC = ({ }, [team.name]) const onUpdateTeamInfoSubmitHandler = () => { - const obj: V2NIMUpdatedTeamInfo = { avatar, name, intro } + const obj: V2NIMUpdateTeamInfoParams = { avatar, name, intro } Object.keys(obj).forEach((key) => { if (obj[key] === team[key]) { 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 618583c..dafb815 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,9 +6,9 @@ import { useTranslation, useStateContext, } from '../../../common' -import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' import { observer } from 'mobx-react' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface GroupItemProps { myMemberInfo: V2NIMTeamMember 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 ba33cba..0f5f73a 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,6 +1,6 @@ import React, { FC, useCallback, useEffect, useMemo, useState } from 'react' import { GroupItem, GroupItemProps } from './GroupItem' -import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' import { Input } from 'antd' import { SearchOutlined } from '@ant-design/icons' import { useStateContext, useTranslation } from '../../../common' 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 38bd1f2..d87525e 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 @@ -8,16 +8,21 @@ import { import { V2NIMTeam, V2NIMTeamMember, - V2NIMUpdatedTeamInfo, -} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' + V2NIMUpdateTeamInfoParams, +} from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' import ChatTeamMemberModal from '../ChatTeamMemberModal' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' import { YxServerExt } from '@xkit-yx/im-store-v2/dist/types/types' export interface GroupPowerProps { - onUpdateTeamInfo: (team: V2NIMUpdatedTeamInfo) => void + onUpdateTeamInfo: (team: V2NIMUpdateTeamInfoParams) => void onTeamMuteChange: (mute: boolean) => void + // 群免打扰变更设置 + // 注意, 现有逻辑取名不准确, 使得设置禁言叫 mute, 设置免打扰也叫 mute + // 为了避免歧义, 在这里回调函数起名叫 disturb 代表和免打扰有关 + onTeamDisturbChange: (disturb: boolean) => void team: V2NIMTeam + teamDoNotDisturbMode: number managers: V2NIMTeamMember[] isGroupOwner: boolean afterSendMsgClick?: () => void 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 2dc79e7..2191518 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 @@ -1,5 +1,5 @@ import React, { FC, useState, useEffect, useMemo } from 'react' -import { Modal, Button, Input } from 'antd' +import { Modal, Button, Input, Switch } from 'antd' import { ExclamationCircleOutlined, RightOutlined, @@ -19,10 +19,10 @@ import { GroupItemProps } from './GroupItem' import { V2NIMTeam, V2NIMTeamMember, - V2NIMUpdatedTeamInfo, + V2NIMUpdateTeamInfoParams, V2NIMUpdateSelfMemberInfoParams, -} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' -import { V2NIMConst } from 'nim-web-sdk-ng' +} from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface HistoryStack { path: GroupSettingType @@ -32,6 +32,7 @@ export interface HistoryStack { export interface ChatTeamSettingProps { members: V2NIMTeamMember[] team: V2NIMTeam + teamDoNotDisturbMode: number myAccount: string isGroupOwner: boolean isGroupManager: boolean @@ -42,9 +43,10 @@ export interface ChatTeamSettingProps { onAddMembersClick: () => void onTransferTeamClick: () => void onRemoveTeamMemberClick: (member: V2NIMTeamMember) => void - onUpdateTeamInfo: (team: V2NIMUpdatedTeamInfo) => void + onUpdateTeamInfo: (team: V2NIMUpdateTeamInfoParams) => void onUpdateMyMemberInfo: (params: V2NIMUpdateSelfMemberInfoParams) => void onTeamMuteChange: (mute: boolean) => void + onTeamDisturbChange: (disturb: boolean) => void afterSendMsgClick?: () => void setNavHistoryStack: (stack: HistoryStack[]) => void renderTeamMemberItem?: ( @@ -64,6 +66,7 @@ const { confirm } = Modal const ChatTeamSetting: FC = ({ members, team, + teamDoNotDisturbMode, myAccount, isGroupOwner, isGroupManager, @@ -76,6 +79,7 @@ const ChatTeamSetting: FC = ({ onUpdateTeamInfo, onUpdateMyMemberInfo, onTeamMuteChange, + onTeamDisturbChange, afterSendMsgClick, setNavHistoryStack, renderTeamMemberItem, @@ -215,7 +219,7 @@ const ChatTeamSetting: FC = ({
{t('teamMemberText')} - ({members.length} ){t('personUnit')} + ({members.length}) {t('personUnit')}
{/* {groupList.length > 6 && } */} @@ -253,6 +257,17 @@ const ChatTeamSetting: FC = ({ placeholder={t('editNickInTeamText')} />
+
+ {t('teamDoNotDisturbText')} + +
{team.teamType !== V2NIMConst.V2NIMTeamType.V2NIM_TEAM_TYPE_INVALID && isOwnerOrManager ? (
= ({ JSX.Element | null | undefined - + msgRecallTime?: number prefix?: string commonPrefix?: string + /** + 是否展示陌生人提示 + */ + strangerTipVisible?: boolean + scrollIntoMode?: 'nearest' + maxUploadFileSize: number } const P2pChatContainer: React.FC = observer( @@ -94,9 +100,12 @@ const P2pChatContainer: React.FC = observer( renderMessageName, renderMessageInnerContent, renderMessageOuterContent, - + maxUploadFileSize, + msgRecallTime = 2 * 60 * 1000, prefix = 'chat', commonPrefix = 'common', + strangerTipVisible = true, + scrollIntoMode, }) => { const { store, nim, localOptions } = useStateContext() @@ -251,7 +260,14 @@ const P2pChatContainer: React.FC = observer( if (_msg) { await getHistory(_msg.createTime, _msg.messageServerId) // 滚动到加载的那条消息 - document.getElementById(_msg.messageClientId)?.scrollIntoView() + document.getElementById(_msg.messageClientId)?.scrollIntoView( + scrollIntoMode == 'nearest' + ? { + block: 'nearest', // 滚动到目标元素的最近可见位置 + inline: 'nearest', // 避免水平方向的滚动 + } + : true + ) } } } @@ -534,7 +550,18 @@ const P2pChatContainer: React.FC = observer( await store.msgStore.deleteMsgActive([msg]) break case 'recall': - await store.msgStore.reCallMsgActive(msg) + try { + const diffTime = Date.now() - msg.createTime + + if (diffTime > msgRecallTime) { + message.error(t('msgRecallTimeErrorText')) + } else { + await store.msgStore.reCallMsgActive(msg) + } + } catch (error) { + logger.error('reCallMsg error', (error as V2NIMError).toString()) + } + break case 'reply': { @@ -578,6 +605,14 @@ const P2pChatContainer: React.FC = observer( break case 'forward': setForwardMessage(msg) + break + case 'voiceToText': + try { + await store.msgStore.voiceToTextActive(msg) + } catch (err) { + message.error(t('voiceToTextFailedText')) + } + break default: break @@ -908,6 +943,7 @@ const P2pChatContainer: React.FC = observer( loadingMore={loadingMore} receiveMsgBtnVisible={receiveMsgBtnVisible} msgReceiptTime={conversation?.msgReceiptTime} + strangerTipVisible={strangerTipVisible} onReceiveMsgBtnClick={scrollToBottom} onMessageAction={onMessageAction} onReeditClick={onReeditClick} @@ -938,6 +974,7 @@ const P2pChatContainer: React.FC = observer( : `${t('sendToText')} ${userNickOrAccount}${t('sendUsageText')}` } translateOpen={translateOpen} + maxUploadFileSize={maxUploadFileSize} onTranslate={setTranslateOpen} replyMsg={replyMsg} mentionMembers={mentionMembers} 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 921b655..f39116c 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 @@ -34,21 +34,21 @@ import { getImgDataUrl, getVideoFirstFrameDataUrl, logger } from '../../utils' import { V2NIMTeam, V2NIMTeamMember, - V2NIMUpdatedTeamInfo, + V2NIMUpdateTeamInfoParams, V2NIMUpdateSelfMemberInfoParams, -} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +} from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' import { V2NIMConversationType, V2NIMConversation, -} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +} from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMConversationService' import { V2NIMMessageForUI, YxReplyMsg, YxServerExt, } 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' -import { V2NIMError } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/types' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' +import { V2NIMMessageNotificationAttachment } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMMessageService' +import { V2NIMError } from 'nim-web-sdk-ng/dist/esm/nim/src/types' import ChatTopMessage from '../components/ChatTopMsg' import { ChatAISearch } from '../components/ChatAISearch' import { ChatAITranslate } from '../components/ChatAITranslate' @@ -91,8 +91,11 @@ export interface TeamChatContainerProps { msg: V2NIMMessageForUI ) => JSX.Element | null | undefined + msgRecallTime?: number prefix?: string commonPrefix?: string + scrollIntoMode?: 'nearest' + maxUploadFileSize: number } const TeamChatContainer: React.FC = observer( @@ -102,6 +105,7 @@ const TeamChatContainer: React.FC = observer( settingActions, actions, msgOperMenu, + maxUploadFileSize, onSendText: onSendTextFromProps, afterTransferTeam, renderTeamCustomMessage, @@ -113,8 +117,10 @@ const TeamChatContainer: React.FC = observer( renderMessageInnerContent, renderMessageOuterContent, + msgRecallTime = 2 * 60 * 1000, prefix = 'chat', commonPrefix = 'common', + scrollIntoMode, }) => { const { store, nim, localOptions } = useStateContext() @@ -126,6 +132,13 @@ const TeamChatContainer: React.FC = observer( const conversation = store.conversationStore.conversations.get(conversationId) + // 群免打扰变更设置 + // 注意, 现有逻辑取名不准确, 使得设置禁言叫 mute, 设置免打扰也叫 mute + // 为了避免歧义, 在这里回调函数起名叫 disturb 代表和免打扰有关 + // 并且之所以需要强转, 是因为 conversation.mute 只有 true/false 布尔值, + // 而群免打扰属性是三态的枚举值, 只不过我们这里忽略了只针对普通群成员设置免打扰的情况 + const teamDoNotDisturbMode = Number(Boolean(conversation?.mute)) + const msgs = store.msgStore.getMsg(conversationId) const replyMsg = store.msgStore.replyMsgs.get(conversationId) @@ -154,9 +167,6 @@ const TeamChatContainer: React.FC = observer( 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(receiverId) @@ -411,7 +421,14 @@ const TeamChatContainer: React.FC = observer( if (_msg) { await getHistory(_msg.createTime, _msg.messageServerId) // 滚动到加载的那条消息 - document.getElementById(_msg.messageClientId)?.scrollIntoView() + document.getElementById(_msg.messageClientId)?.scrollIntoView( + scrollIntoMode == 'nearest' + ? { + block: 'nearest', // 滚动到目标元素的最近可见位置 + inline: 'nearest', // 避免水平方向的滚动 + } + : true + ) } } } @@ -762,8 +779,20 @@ const TeamChatContainer: React.FC = observer( await store.msgStore.deleteMsgActive([msg]) break case 'recall': - await store.msgStore.reCallMsgActive(msg) + try { + const diffTime = Date.now() - msg.createTime + + if (diffTime > msgRecallTime) { + message.error(t('msgRecallTimeErrorText')) + } else { + await store.msgStore.reCallMsgActive(msg) + } + } catch (error) { + logger.error('reCallMsg error', (error as V2NIMError).toString()) + } + break + case 'reply': { const member = mentionMembers.find( @@ -814,6 +843,14 @@ const TeamChatContainer: React.FC = observer( break case 'unTop': handleTopMessage(msg, false) + break + case 'voiceToText': + try { + await store.msgStore.voiceToTextActive(msg) + } catch (err) { + message.error(t('voiceToTextFailedText')) + } + break default: break @@ -958,7 +995,7 @@ const TeamChatContainer: React.FC = observer( ) const onUpdateTeamInfo = useCallback( - async (params: V2NIMUpdatedTeamInfo) => { + async (params: V2NIMUpdateTeamInfoParams) => { try { await store.teamStore.updateTeamActive({ teamId: team.teamId, @@ -1032,6 +1069,27 @@ const TeamChatContainer: React.FC = observer( [store.teamStore, team.teamId, t] ) + // 群免打扰的修改. + const onTeamDisturbChange = useCallback( + async (mute: boolean) => { + try { + await store.teamStore.setTeamMessageMuteModeActive( + team.teamId, + team.teamType, + mute + ? V2NIMConst.V2NIMTeamMessageMuteMode + .V2NIM_TEAM_MESSAGE_MUTE_MODE_ON + : V2NIMConst.V2NIMTeamMessageMuteMode + .V2NIM_TEAM_MESSAGE_MUTE_MODE_OFF + ) + message.success(t('updateTeamSuccessText')) + } catch (error) { + message.error(t('updateTeamFailedText')) + } + }, + [store.teamStore, team.teamId, team.teamType, t] + ) + const handleForwardModalSend = () => { scrollToBottom() setForwardMessage(undefined) @@ -1078,6 +1136,10 @@ const TeamChatContainer: React.FC = observer( .finally(() => { visibilityObserver.unobserve(params.target) }) + // 会话列表@消息判断需要,标记当前会话最后已读时间 + store.conversationStore.markConversationReadActive( + msg.conversationId + ) } } } @@ -1369,12 +1431,30 @@ const TeamChatContainer: React.FC = observer( .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_APPLY_PASS: { if (msg.senderId === myUser.accountId) { - message.info( - `${store.uiStore.getAppellation({ - account: msg.senderId, - teamId: msg.receiverId, - })}${t('enterTeamText')}` - ) + const accounts: string[] = attachment?.targetIds || [] + const nicks = accounts + .map((item) => { + if ( + !( + msg.conversationType !== + V2NIMConst.V2NIMConversationType + .V2NIM_CONVERSATION_TYPE_TEAM || + store.userStore.users.has(item) || + store.aiUserStore.aiUsers.has(item) + ) + ) { + store.userStore.getUserActive(item) + } + + return store.uiStore.getAppellation({ + account: item, + teamId: msg.receiverId, + }) + }) + .filter((item) => !!item) + .join('、') + + message.info(`${nicks}${t('enterTeamText')}`) } } @@ -1484,6 +1564,7 @@ const TeamChatContainer: React.FC = observer( ref={chatMessageInputRef} prefix={prefix} commonPrefix={commonPrefix} + maxUploadFileSize={maxUploadFileSize} placeholder={ renderTeamInputPlaceHolder ? renderTeamInputPlaceHolder({ @@ -1522,6 +1603,7 @@ const TeamChatContainer: React.FC = observer( = observer( onUpdateTeamInfo={onUpdateTeamInfo} onUpdateMyMemberInfo={onUpdateMyMemberInfo} onTeamMuteChange={onTeamMuteChange} + onTeamDisturbChange={onTeamDisturbChange} onTransferTeamClick={onTransferMemberClick} renderTeamMemberItem={renderTeamMemberItem} prefix={prefix} diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/CommonIcon/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/CommonIcon/index.tsx index f3f3473..8baa08c 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/CommonIcon/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/CommonIcon/index.tsx @@ -2,8 +2,8 @@ import { createFromIconfontCN } from '@ant-design/icons' const CommonIcon = createFromIconfontCN({ scriptUrl: [ - 'https://yx-web-nosdn.netease.im/common/b38b4b232eb3933d8cb7ea16b2464f05/yunxun-imweb-iconfont.js', - 'https://at.alicdn.com/t/c/font_3429868_fwpfhemf2p.js', + '//yx-web-nosdn.netease.im/common/b38b4b232eb3933d8cb7ea16b2464f05/yunxun-imweb-iconfont.js', + '//at.alicdn.com/t/c/font_3429868_fwpfhemf2p.js', ], // 在 iconfont.cn 上生成 }) 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 6c6036b..f863c7e 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 @@ -22,8 +22,8 @@ import { 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' +} from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMMessageService' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' import { useTranslation, useStateContext } from '../../index' import { observer } from 'mobx-react' import { getBlobImg } from '../../../urlToBlob' @@ -31,8 +31,8 @@ import { V2NIMMessageForUI, YxServerExt, } from '@xkit-yx/im-store-v2/dist/types/types' -import { V2NIMConst } from 'nim-web-sdk-ng' -import { V2NIMError } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/types' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' +import { V2NIMError } from 'nim-web-sdk-ng/dist/esm/nim/src/types' // 对话框中要展示的文件icon标识 const fileIconMap = { @@ -608,14 +608,21 @@ export const ParseSession: React.FC = observer( // 申请加入群聊成功 case V2NIMConst.V2NIMMessageNotificationType .V2NIM_MESSAGE_NOTIFICATION_TYPE_TEAM_APPLY_PASS: { - getUserInfo(msg.senderId) + const accounts: string[] = attachment?.targetIds || [] + const nicks = accounts + .map((item) => { + getUserInfo(item) + return store.uiStore.getAppellation({ + account: item, + teamId, + }) + }) + .filter((item) => !!item) + .join('、') + return (
- {store.uiStore.getAppellation({ - account: msg.senderId, - teamId, - })}{' '} - {t('joinTeamText')} + {nicks} {t('joinTeamText')}
) } @@ -808,7 +815,10 @@ export const ParseSession: React.FC = observer( ) } - const renderAudio = (msg: V2NIMMessageForUI) => { + const renderAudio = ( + msg: V2NIMMessageForUI, + shouldRenderTextOfVoice = true + ) => { const attachment = msg.attachment as V2NIMMessageAudioAttachment const duration = Math.floor(attachment?.duration / 1000) || 0 @@ -817,7 +827,11 @@ export const ParseSession: React.FC = observer( return (
= observer(
+ {shouldRenderTextOfVoice && msg.textOfVoice && ( +
{msg.textOfVoice}
+ )}
) } @@ -1019,7 +1036,8 @@ export const ParseSession: React.FC = observer( return renderNotification(msg) case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_AUDIO: getUserInfo(msg.senderId) - return renderAudio(msg) + // 回复的语音消息应测试要求不要渲染转文字内容 + return renderAudio(msg, !isReplyMsg) case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL: return `[${t('callMsgText')},${notSupportMessageText}]` case V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_LOCATION: @@ -1045,6 +1063,10 @@ export const ParseSession: React.FC = observer( } } + const renderAudioToText = () => { + return
+ } + return ( <> {renderReplyMsg()} diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/style/index.less b/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/style/index.less index 54ee769..0f17697 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/CommonParseSession/style/index.less @@ -61,6 +61,10 @@ padding: 12px 16px; display: flex; flex-direction: column; + max-width: 100%; +} +.@{prefix}-audio-text { + margin-top: 15px; } .@{prefix}-audio-in, .@{prefix}-audio-out { @@ -183,7 +187,7 @@ } .@{prefix}-map-menu { - width: 60px; + max-width: 85px; a { color: @yx-text-color-1; display: block; 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 feb3d88..c2540a5 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 @@ -5,7 +5,7 @@ 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' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export type ComplexAvatarContainerProps = Pick< ComplexAvatarProps, diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/CreateTeamModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/CreateTeamModal/index.tsx index e9a4000..8906266 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/CreateTeamModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/CreateTeamModal/index.tsx @@ -7,7 +7,7 @@ import { useTranslation, useStateContext, } from '../../' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' import { observer } from 'mobx-react' const emptyArr = [] @@ -117,7 +117,7 @@ export const CreateTeamModal: React.FC = observer( visible={visible} onCancel={onCancel} destroyOnClose={true} - width={530} + width={630} footer={footer} >
diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/CreateTeamModal/style/index.less b/react/src/YXUIKit/im-kit-ui/src/common/components/CreateTeamModal/style/index.less index e71c0b6..5a59fb0 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/CreateTeamModal/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/CreateTeamModal/style/index.less @@ -16,7 +16,7 @@ align-items: center; &-content { - width: 80px; + min-width: 80px; height: 16px; text-align: center; font-family: 'PingFang SC'; @@ -40,7 +40,7 @@ align-items: center; &-content { - width: 85px; + min-width: 85px; height: 16px; text-align: center; font-family: 'PingFang SC'; 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 9618c68..b878e49 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 @@ -2,6 +2,7 @@ import React, { FC, useEffect, useMemo, useState } from 'react' import { Avatar, Badge } from 'antd' import { Storage } from '@xkit-yx/utils' import { urls } from '../GroupAvatarSelect' +import { getAvatarBackgroundColor } from '../../../utils' export interface CrudeAvatarProps { account: string @@ -40,26 +41,40 @@ export const CrudeAvatar: FC = ({ return (nick || account || '').slice(-2) }, [nick, account]) - useEffect(() => { - const colorMap: { [key: number]: string } = { - 0: '#60CFA7', - 1: '#53C3F3', - 2: '#537FF4', - 3: '#854FE2', - 4: '#BE65D9', - 5: '#E9749D', - 6: '#F9B751', + const avatarStyle = useMemo(() => { + if (webAvatar && !imgFailed) { + return { + verticalAlign: 'middle', + } } - const store = new Storage('localStorage', '__xkit__') - const key = `avatarColor-${account}` - let bgColor = store.get(key) - if (!bgColor) { - bgColor = colorMap[Math.floor(Math.random() * 7)] - store.set(key, bgColor) + return { + backgroundColor: bgColor, + verticalAlign: 'middle', } + }, [webAvatar, imgFailed, bgColor]) + + useEffect(() => { + try { + /** + * 原实现方案,改为和移动端统一方案 + const store = new Storage('localStorage', '__xkit__') + const key = `avatarColor-${account}` + let bgColor = store.get(key) + + if (!bgColor) { + // 此处计算方式待优化 + bgColor = colorMap[Math.floor(Math.random() * 7)] + store.set(key, bgColor) + } + */ + + const bgColor = getAvatarBackgroundColor(account) - setBgColor(bgColor) + setBgColor(bgColor) + } catch (error) { + console.log('CrudeAvatar avatarColor: ', error) + } }, [account]) useEffect(() => { @@ -73,19 +88,6 @@ export const CrudeAvatar: FC = ({ setWebAvatar(webUrl ? webUrl : avatar) }, [avatar]) - const avatarStyle = useMemo(() => { - if (webAvatar && !imgFailed) { - return { - verticalAlign: 'middle', - } - } - - return { - backgroundColor: bgColor, - verticalAlign: 'middle', - } - }, [webAvatar, imgFailed, bgColor]) - return ( = ({ overflowCount={99} showZero={false} > - { - setImgFailed(true) - return true - }} - > - {text} - + {webAvatar ? ( + { + setImgFailed(true) + return true + }} + > + {text} + + ) : ( +
+ {text} +
+ )}
) } 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 deleted file mode 100644 index 21f2684..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/Container.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import React, { FC } from 'react' -import { FriendSelectUI, FriendSelectUIProps } from './FriendSelectUI' -import { useStateContext } from '../../hooks/useStateContext' -import { observer } from 'mobx-react' - -export type FriendSelectContainerProps = Omit - -export const FriendSelectContainer: FC = observer( - (props) => { - const { store } = useStateContext() - - const friendsWithoutBlacklist = store.uiStore.friends - .filter((item) => !store.relationStore.blacklist.includes(item.accountId)) - .map((item) => item.accountId) - - return - } -) 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 deleted file mode 100644 index 346ec14..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import React, { FC, useMemo } from 'react' -import { Divider, message, Spin } from 'antd' -import { FriendSelectItem } from './FriendSelectItem' -import { groupByPy } from '../../../utils' -import { useTranslation } from '../../hooks/useTranslation' -import { useStateContext } from '../../hooks/useStateContext' - -export interface FriendSelectUIProps { - accounts?: string[] - selectedAccounts: string[] - onSelect: (accounts: string[]) => void - loading?: boolean - max?: number - - prefix?: string -} - -export const FriendSelectUI: FC = ({ - accounts = [], - selectedAccounts, - onSelect, - loading = false, - max, - prefix = 'common', -}) => { - const _prefix = `${prefix}-friend-select` - - const { t } = useTranslation() - const { store } = useStateContext() - - const dataSource = useMemo(() => { - const _data = accounts.map((account) => ({ - account, - appellation: store.uiStore.getAppellation({ account }), - })) - return groupByPy( - _data, - { - firstKey: 'appellation', - }, - false - ) - }, [accounts, store.uiStore]) - - const handleSelect = (account: string, selected: boolean) => { - let _selectedAccounts: string[] = [] - if (selected && !selectedAccounts.includes(account)) { - if (max && selectedAccounts.length >= max) { - message.error(`${t('maxSelectedText')}${max}${t('friendsText')}`) - return - } - _selectedAccounts = selectedAccounts.concat(account) - } else if (!selected && selectedAccounts.includes(account)) { - _selectedAccounts = selectedAccounts.filter((item) => item !== account) - } - const _selectedList = accounts.filter((item) => - _selectedAccounts.includes(item) - ) - onSelect(_selectedList) - } - - const selectedList = useMemo(() => { - return accounts.filter((item) => selectedAccounts.includes(item)) - }, [accounts, selectedAccounts]) - - const strangerList = useMemo(() => { - return selectedAccounts.filter((item) => accounts.every((j) => j !== item)) - }, [accounts, selectedAccounts]) - - return ( -
- {loading ? ( - - ) : ( - <> -
- {dataSource.map(({ key, data }) => { - return ( -
-
{key}
- {data.map((item) => ( - - ))} -
- ) - })} -
- -
-
- {t('selectedText')}:{selectedList.length} {t('friendsText')} - {strangerList.length ? ( - <> - ,{strangerList.length} {t('strangerText')} - - ) : null} -
-
- {selectedList.map((item) => ( - - ))} -
-
- - )} -
- ) -} diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/index.tsx index aea2fcf..612a9db 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/index.tsx @@ -141,24 +141,46 @@ export const FriendSelect: FC = observer( ) : null } - const rowRenderer = useCallback( + const friendSelectedRowRenderer = useCallback( ({ index, key, style }) => { - const item = dataSource.groupByPyData[index] + const friend = selectedList[index] return (
+
+ ) + }, + [prefix, selectedList, selectedAccounts] + ) + + const friendRowRenderer = useCallback( + ({ index, key, style }) => { + const friend = dataSource.groupByPyData[index] + + return ( +
+ = max && - !selectedAccounts.includes(item.account)) + disabledAccounts.includes(friend.account) + // || + // (selectedAccounts.length >= max && + // !selectedAccounts.includes(friend.account)) } prefix={prefix} - {...item} + {...friend} />
) @@ -189,7 +211,7 @@ export const FriendSelect: FC = observer( overscanRowCount={10} rowCount={dataSource.groupByPyData.length} rowHeight={48} - rowRenderer={rowRenderer} + rowRenderer={friendRowRenderer} width={width} /> )} @@ -207,17 +229,18 @@ export const FriendSelect: FC = observer( ) : null}
- {selectedList.map((item) => ( - - ))} + + {({ height, width }) => ( + + )} +
diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/style/index.less b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/style/index.less index 5e77ec9..1b53075 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/style/index.less @@ -70,7 +70,7 @@ &-tabs { .@{ant-prefix}-tabs-nav-list { - width: 100%; + width: 240px !important; justify-content: space-between; } diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/GroupAvatarSelect/style/index.less b/react/src/YXUIKit/im-kit-ui/src/common/components/GroupAvatarSelect/style/index.less index 3153ee8..bae0f58 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/GroupAvatarSelect/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/GroupAvatarSelect/style/index.less @@ -31,4 +31,4 @@ width: 100%; height: 100%; } -} +} \ No newline at end of file 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 6a364d6..87dc460 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 @@ -7,7 +7,7 @@ import { message } from 'antd' import { useStateContext } from '../../hooks/useStateContext' import { useTranslation } from '../../hooks/useTranslation' import { observer } from 'mobx-react' -import { V2NIMUserUpdateParams } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMUserService' +import { V2NIMUserUpdateParams } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMUserService' import { Gender } from '../UserCard' export type MyAvatarContainerProps = Pick< diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/MyUserCard/style/index.less b/react/src/YXUIKit/im-kit-ui/src/common/components/MyUserCard/style/index.less index a3e2bbe..514c04c 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/MyUserCard/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/MyUserCard/style/index.less @@ -68,4 +68,4 @@ color: @yx-primary-text-color; } } -} +} \ No newline at end of file diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/ReadPercent/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/ReadPercent/index.tsx index b62c0c1..b1f8372 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/ReadPercent/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/ReadPercent/index.tsx @@ -1,33 +1,30 @@ -import React from 'react' +import React, { useMemo } from 'react' import { useTranslation } from '../../hooks/useTranslation' import { Popover } from 'antd' -import CommonIcon from '../CommonIcon' +import { CheckCircleOutlined } from '@ant-design/icons' export interface ReadPercentProps { unread: number read: number hoverable?: boolean - radius?: number - + size?: number prefix?: string } export const ReadPercent: React.FC = ({ unread, read, - radius = 8, + size = 8, hoverable = false, prefix = 'common', }) => { const _prefix = `${prefix}-percent` const { t } = useTranslation() - const percent = (read / (unread + read)) * 100 || 0 - const renderDetail = () => { return ( - {percent >= 100 + {percent >= 360 ? t('allReadText') : `${t('unreadText')} ${unread} ${t('personUnit')} | ${t( 'readText' @@ -36,38 +33,89 @@ export const ReadPercent: React.FC = ({ ) } - const renderSvg = () => { - return percent >= 100 ? ( - - ) : ( - - - - + // const renderSvg = () => { + // return percent >= 100 ? ( + // + // ) : ( + // + // + // + // + // ) + // } + + const percent = useMemo(() => { + return (read / (unread + read)) * 360 || 0 + }, [read, unread]) + + const renderReadPercent = () => { + return ( +
+ {percent >= 360 ? ( + + ) : percent == 0 ? ( +
+
+
+ ) : ( +
+ + = 180 ? 'cover-2 cover-3' : 'cover-2'} + > +
+ )} +
) } @@ -75,10 +123,10 @@ export const ReadPercent: React.FC = ({
{hoverable ? ( - {renderSvg()} + {renderReadPercent()} ) : ( - renderSvg() + renderReadPercent() )}
) diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/ReadPercent/style/index.less b/react/src/YXUIKit/im-kit-ui/src/common/components/ReadPercent/style/index.less index 6558d30..5367da4 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/ReadPercent/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/ReadPercent/style/index.less @@ -6,7 +6,7 @@ .@{percent-prefix-cls} { display: flex; align-items: center; - + justify-content: center; &-icon { color: @yx-avatar-color-3!important; } @@ -23,4 +23,54 @@ stroke: @yx-avatar-color-3; } } + &-read-icon-wrap { + display: flex; + align-items: center; + justify-content: center; + } + &-read-icon { + font-size: @yx-font-size-16; + color: @yx-text-color-4 !important; + position: relative; + } + + &-unread-icon { + border-radius: 50%; + position: relative; + background-color: @yx-text-color-4; + display: flex; + align-items: center; + justify-content: center; + } + &-unread-icon-inner { + border-radius: 50%; + background-color: @yx-primary-color-outline; + } + &-sector { + display: inline-block; + position: relative; + overflow: hidden; + border: 1px solid @yx-text-color-4; + background-color: @yx-primary-color-outline; + border-radius: 50%; + + .cover-1, + .cover-2 { + position: absolute; + top: 0; + width: 50%; + height: 100%; + background-color: @yx-primary-color-outline; + } + + .cover-1 { + background-color: @yx-text-color-4; + transform-origin: right; + } + + .cover-3 { + right: 0; + background-color: @yx-text-color-4; + } + } } diff --git a/react/src/YXUIKit/im-kit-ui/src/common/constant.ts b/react/src/YXUIKit/im-kit-ui/src/common/constant.ts deleted file mode 100644 index f2c478b..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/common/constant.ts +++ /dev/null @@ -1,90 +0,0 @@ -interface IKeyMap { - [key: string]: string -} - -export const EMOJI_ICON_MAP_CONFIG: IKeyMap = { - '[大笑]': 'icon-a-1', - '[开心]': 'icon-a-2', - '[色]': 'icon-a-3', - '[酷]': 'icon-a-4', - '[奸笑]': 'icon-a-5', - '[亲]': 'icon-a-6', - '[伸舌头]': 'icon-a-7', - '[眯眼]': 'icon-a-8', - '[可爱]': 'icon-a-9', - '[鬼脸]': 'icon-a-10', - '[偷笑]': 'icon-a-11', - '[喜悦]': 'icon-a-12', - '[狂喜]': 'icon-a-13', - '[惊讶]': 'icon-a-14', - '[流泪]': 'icon-a-15', - '[流汗]': 'icon-a-16', - '[天使]': 'icon-a-17', - '[笑哭]': 'icon-a-18', - '[尴尬]': 'icon-a-19', - '[惊恐]': 'icon-a-20', - '[大哭]': 'icon-a-21', - '[烦躁]': 'icon-a-22', - '[恐怖]': 'icon-a-23', - '[两眼冒星]': 'icon-a-24', - '[害羞]': 'icon-a-25', - '[睡着]': 'icon-a-26', - '[冒星]': 'icon-a-27', - '[口罩]': 'icon-a-28', - '[OK]': 'icon-a-29', - '[好吧]': 'icon-a-30', - '[鄙视]': 'icon-a-31', - '[难受]': 'icon-a-32', - '[不屑]': 'icon-a-33', - '[不舒服]': 'icon-a-34', - '[愤怒]': 'icon-a-35', - '[鬼怪]': 'icon-a-36', - '[发怒]': 'icon-a-37', - '[生气]': 'icon-a-38', - '[不高兴]': 'icon-a-39', - '[皱眉]': 'icon-a-40', - '[心碎]': 'icon-a-41', - '[心动]': 'icon-a-42', - '[好的]': 'icon-a-43', - '[低级]': 'icon-a-44', - '[赞]': 'icon-a-45', - '[鼓掌]': 'icon-a-46', - '[给力]': 'icon-a-47', - '[打你]': 'icon-a-48', - '[阿弥陀佛]': 'icon-a-49', - '[拜拜]': 'icon-a-50', - '[第一]': 'icon-a-51', - '[拳头]': 'icon-a-52', - '[手掌]': 'icon-a-53', - '[剪刀]': 'icon-a-54', - '[招手]': 'icon-a-55', - '[不要]': 'icon-a-56', - '[举着]': 'icon-a-57', - '[思考]': 'icon-a-58', - '[猪头]': 'icon-a-59', - '[不听]': 'icon-a-60', - '[不看]': 'icon-a-61', - '[不说]': 'icon-a-62', - '[猴子]': 'icon-a-63', - '[炸弹]': 'icon-a-64', - '[睡觉]': 'icon-a-65', - '[筋斗云]': 'icon-a-66', - '[火箭]': 'icon-a-67', - '[救护车]': 'icon-a-68', - '[便便]': 'icon-a-70', -} - -// TODO react-string-replace 的行为不是那么的好理解,比如像下面这个正则就一定要加 '()',后面最好干掉自己实现 -export const INPUT_EMOJI_SYMBOL_REG = new RegExp( - '(' + - Object.keys(EMOJI_ICON_MAP_CONFIG) - .map((item) => { - const left = `\\${item.slice(0, 1)}` - const right = `\\${item.slice(-1)}` - const mid = item.slice(1, -1) - return `${left}${mid}${right}` - }) - .join('|') + - ')', - 'g' -) 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 731c27c..9abeea7 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 @@ -11,12 +11,12 @@ 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 { useStateContext } from '../hooks/useStateContext' -import V2NIM, { V2NIMConst } from 'nim-web-sdk-ng' +import { NIM, V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' import zh from '../locales/zh' import sdkPkg from 'nim-web-sdk-ng/package.json' export interface ContextProps { - nim?: V2NIM + nim?: NIM store?: RootStore localOptions?: Partial t?: (str: keyof typeof zh) => string @@ -25,7 +25,7 @@ export interface ContextProps { export interface ProviderProps { children: ReactNode localOptions?: Partial - nim: V2NIM + nim: NIM // 单例模式,用于 vue 带 UI 组件 singleton?: boolean locale?: 'zh' | 'en' @@ -58,83 +58,84 @@ const defaultLocalOptions: Required = { sendMsgBefore: async (options: any) => options, aiUserAgentProvider: {}, conversationLimit: 100, + debug: 'debug', } -export const Provider: FC = memo( - ({ - children, - localOptions = defaultLocalOptions, - nim, - locale = 'zh', - localeConfig = zh, - renderImDisConnected, - renderImConnecting, - singleton = false, - }) => { - const localeMap = useMemo( - () => ({ - zh, - }), - [] - ) - - const t = useCallback( - (str: keyof typeof zh) => { - return { - ...(localeMap[locale] || zh), - ...localeConfig, - }[str] - }, - [locale, localeConfig, localeMap] - ) +export const Provider: FC = memo(function Main({ + children, + localOptions = defaultLocalOptions, + nim, + locale = 'zh', + localeConfig = zh, + renderImDisConnected, + renderImConnecting, + singleton = false, +}) { + const localeMap = useMemo( + () => ({ + zh, + }), + [] + ) - const finalLocalOptions = useMemo(() => { - return { ...defaultLocalOptions, ...localOptions } - }, [localOptions]) + const t = useCallback( + (str: keyof typeof zh) => { + return { + ...(localeMap[locale] || zh), + ...localeConfig, + }[str] + }, + [locale, localeConfig, localeMap] + ) - const rootStore = useMemo(() => { - if (singleton) { - return RootStore.getInstance(nim, finalLocalOptions) - } + const finalLocalOptions = useMemo(() => { + return { ...defaultLocalOptions, ...localOptions } + }, [localOptions]) - return new RootStore(nim, finalLocalOptions) - }, [nim, singleton, finalLocalOptions]) + const rootStore = useMemo(() => { + if (singleton) { + // @ts-ignore + return RootStore.getInstance(nim, finalLocalOptions) + } // @ts-ignore - window.__xkit_store__ = { - nim, - store: rootStore, - localOptions: finalLocalOptions, - sdkVersion: sdkPkg.version, - } + return new RootStore(nim, finalLocalOptions) + }, [nim, singleton, finalLocalOptions]) - useEffect(() => { - return () => { - if (!singleton) { - rootStore.destroy() - } + // @ts-ignore + window.__xkit_store__ = { + nim, + store: rootStore, + localOptions: finalLocalOptions, + sdkVersion: sdkPkg.version, + } + + useEffect(() => { + return () => { + if (!singleton) { + rootStore.destroy() } - }, [rootStore, singleton]) + } + }, [rootStore, singleton]) - return ( - + - - {children} - - - ) - } -) + {children} + + + ) +}) export const App: FC<{ renderImDisConnected?: () => JSX.Element 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 99eb1e4..cea28e2 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 @@ -12,6 +12,8 @@ const LocaleConfig = { cancelText: '取消', deleteText: '删除', recallText: '撤回', + voiceToText: '语音转文字', + voiceToTextFailedText: '转文字失败', forwardText: '转发', forwardSuccessText: '转发成功', forwardFailedText: '转发失败', @@ -186,6 +188,7 @@ const LocaleConfig = { collectionSuccess: '已收藏', collectionFailed: '收藏失败', getCollectionFailed: '查询收藏列表失败', + msgRecallTimeErrorText: '已超过时间无法撤回', removeCollectionSuccess: '删除收藏成功', removeCollectionFailed: '删除收藏失败', confirmRemoveCollection: '确认删除收藏?', @@ -195,6 +198,7 @@ const LocaleConfig = { enterTeamText: '进入了群组', leaveTeamText: '离开了群组', teamMuteText: '群禁言', + teamDoNotDisturbText: '群免打扰', muteAllTeamSuccessText: '开启全员禁言成功', unmuteAllTeamSuccessText: '结束全员禁言成功', muteAllTeamFailedText: '开启全员禁言失败', @@ -300,7 +304,7 @@ const LocaleConfig = { aiSendingText: '大模型请求响应中', searchTipText: 'Enter 搜索', - // emoji 不能随便填,要用固定 key,,参考 demo + // emoji 不能随便填,要用固定 key,参考 demo Laugh: '[大笑]', Happy: '[开心]', Sexy: '[色]', diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/Container.tsx index af6b077..11a17ab 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/Container.tsx @@ -4,7 +4,7 @@ import { useEventTracking, useStateContext } from '../../common' import packageJson from '../../../package.json' import { observer } from 'mobx-react' import sdkPkg from 'nim-web-sdk-ng/package.json' -import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMAIService' +import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMAIService' export interface AIListContainerProps { /** diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/components/AIItem.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/components/AIItem.tsx index d57a98e..95bf5ef 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/components/AIItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/components/AIItem.tsx @@ -1,7 +1,7 @@ import React, { FC } from 'react' import { ComplexAvatarContainer, useStateContext } from '../../../common' import { observer } from 'mobx-react' -import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMAIService' +import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMAIService' export interface AIItemProps { aiUser: V2NIMAIUser diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/components/AIList.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/components/AIList.tsx index 1a3d571..f6d5643 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/components/AIList.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/ai-list/components/AIList.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react' import { AIItem } from './AIItem' import { useTranslation } from '../../../common' import { Spin, Empty } from 'antd' -import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMAIService' +import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMAIService' export interface AIListProps { list: V2NIMAIUser[] 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 600d25a..5e024ad 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 @@ -8,13 +8,13 @@ import { MsgListContainer } from '../msg-list/Container' import { AIListContainer } from '../ai-list/Container' import packageJson from '../../../package.json' -import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' import sdkPgk from 'nim-web-sdk-ng/package.json' import { V2NIMFriendAddApplicationForUI, V2NIMTeamJoinActionInfoForUI, } from '@xkit-yx/im-store-v2/dist/types/types' -import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMAIService' +import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMAIService' export interface ContactInfoContainerProps { /** diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/style/contactItem.less b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/style/contactItem.less index 9511e6a..263d4a6 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/style/contactItem.less +++ b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/style/contactItem.less @@ -19,5 +19,6 @@ &-label { margin-left: 12px; font-size: @yx-primary-font-size; + white-space: nowrap; } } diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/style/contactList.less b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/style/contactList.less index 6b8b025..a68fe05 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/style/contactList.less +++ b/react/src/YXUIKit/im-kit-ui/src/contact/contact-list/style/contactList.less @@ -7,4 +7,4 @@ width: 100%; height: 100%; overflow-y: auto; -} +} \ No newline at end of file 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 bfad987..ec00a21 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,11 +1,11 @@ import React, { FC, useCallback } from 'react' import { GroupList } from './components/GroupList' import { useEventTracking, useStateContext } from '../../common' -import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/esm/nim/src/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' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface GroupListContainerProps { /** 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 db86b59..86f2584 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,6 +1,6 @@ import React, { FC } from 'react' import { CrudeAvatar } from '../../../common' -import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' export interface GroupItemProps extends V2NIMTeam { onItemClick?: (team: V2NIMTeam) => void 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 3493edc..8886382 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,6 +1,6 @@ import React, { FC, useCallback } from 'react' import { GroupItem } from './GroupItem' -import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' import { useTranslation } from '../../../common' import { Spin, Empty } from 'antd' import { AutoSizer, List } from 'react-virtualized' 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 c47954e..0e90ec0 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,7 @@ import packageJson from '../../../package.json' import { observer } from 'mobx-react' import { message } from 'antd' import { logger } from '../../utils' +// todo v10.6.0 import sdkPkg from 'nim-web-sdk-ng/package.json' import { V2NIMFriendAddApplicationForUI, 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 cdf30c7..f390598 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 @@ -11,7 +11,7 @@ import { V2NIMFriendAddApplicationForUI, V2NIMTeamJoinActionInfoForUI, } from '@xkit-yx/im-store-v2/dist/types/types' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export enum TMsgItemType { FRIEND = 'friend', 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 be907aa..df8b5bc 100644 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/conversation/Container.tsx @@ -6,9 +6,10 @@ import { import { useStateContext, useEventTracking } from '../common' import packageJson from '../../package.json' import { observer } from 'mobx-react' -import { V2NIMConversation } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' +import { V2NIMConversation } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMConversationService' +// todo v10.6.0 可以静态方法里取 import sdkPkg from 'nim-web-sdk-ng/package.json' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' import { PinAIList } from './components/pinAIList' export interface ConversationContainerProps { @@ -112,7 +113,26 @@ export const ConversationContainer: FC = observer( const handleConversationItemClick = async ( conversation: V2NIMConversation ) => { + // 处理@消息相关 + if ( + conversation.type === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM || + conversation.type === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_SUPER_TEAM + ) { + await store.conversationStore.updateConversation([ + { + ...conversation, + beMentioned: false, + }, + ]) + await store.conversationStore.markConversationReadActive( + conversation.conversationId + ) + } + await store.uiStore.selectConversation(conversation.conversationId) + onConversationItemClick?.(conversation.conversationId) } @@ -140,17 +160,55 @@ export const ConversationContainer: FC = observer( conversation: V2NIMConversation, mute: boolean ) => { - await store.relationStore.setP2PMessageMuteModeActive( + const conversationType = + nim.V2NIMConversationIdUtil.parseConversationType( + conversation.conversationId + ) + const conversationTarget = nim.V2NIMConversationIdUtil.parseConversationTargetId( conversation.conversationId - ), - mute - ? V2NIMConst.V2NIMP2PMessageMuteMode.V2NIM_P2P_MESSAGE_MUTE_MODE_ON - : V2NIMConst.V2NIMP2PMessageMuteMode.V2NIM_P2P_MESSAGE_MUTE_MODE_OFF - ) + ) + + if ( + conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P + ) { + await store.relationStore.setP2PMessageMuteModeActive( + conversationTarget, + mute + ? V2NIMConst.V2NIMP2PMessageMuteMode.V2NIM_P2P_MESSAGE_MUTE_MODE_ON + : V2NIMConst.V2NIMP2PMessageMuteMode.V2NIM_P2P_MESSAGE_MUTE_MODE_OFF + ) + } else { + await store.teamStore.setTeamMessageMuteModeActive( + conversationTarget, + conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM + ? V2NIMConst.V2NIMTeamType.V2NIM_TEAM_TYPE_ADVANCED + : V2NIMConst.V2NIMTeamType.V2NIM_TEAM_TYPE_SUPER, + mute + ? V2NIMConst.V2NIMTeamMessageMuteMode + .V2NIM_TEAM_MESSAGE_MUTE_MODE_ON + : V2NIMConst.V2NIMTeamMessageMuteMode + .V2NIM_TEAM_MESSAGE_MUTE_MODE_OFF + ) + } + + // V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P + // ? onP2pConversationItemMuteChange?.(conversation.conversationId, mute) + // : onTeamConversationItemMuteChange?.(conversation.conversationId, mute) onConversationItemMuteChange?.(conversation.conversationId, mute) } + const handleLoadMoreConversations = () => { + const offset = + store.uiStore.conversations[store.uiStore.conversations.length - 1] + ?.sortOrder + const limit = store.localOptions.conversationLimit + + store.conversationStore.getConversationListActive(offset, limit) + } + const conversations = useMemo(() => { return store.uiStore.conversations.sort( (a, b) => b.sortOrder - a.sortOrder @@ -180,6 +238,7 @@ export const ConversationContainer: FC = observer( renderConversationMsg={renderConversationMsg} prefix={prefix} commonPrefix={commonPrefix} + handleLoadMoreConversations={handleLoadMoreConversations} />
) 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 3f12bbd..8f4dab3 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,8 +7,8 @@ import { getMsgContentTipByType, useTranslation, } from '../../common' -import { V2NIMLastMessage } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMConversationService' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMLastMessage } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMConversationService' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface ConversationItemProps { isTop: boolean 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 26e5815..0d85b8b 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,9 +1,10 @@ -import React from 'react' +import React, { useCallback } from 'react' 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' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' +import { AutoSizer, List } from 'react-virtualized' export type ConversationCallbackProps = { onConversationItemClick: (conversation: V2NIMConversationForUI) => void @@ -22,6 +23,7 @@ export type ConversationListProps = { conversations: V2NIMConversationForUI[] loading?: boolean selectedConversation?: string + handleLoadMoreConversations: () => void renderCustomTeamConversation?: ( options: { conversation: V2NIMConversationForUI } & Omit< ConversationCallbackProps, @@ -55,6 +57,7 @@ export const ConversationList: React.FC = ({ conversations, loading = false, selectedConversation, + handleLoadMoreConversations, onConversationItemClick, onConversationItemDeleteClick, onConversationItemStickTopChange, @@ -69,16 +72,33 @@ export const ConversationList: React.FC = ({ prefix = 'conversation', commonPrefix = 'common', }) => { - return ( -
- {loading ? ( - - ) : !conversations.length ? ( - renderConversationListEmpty?.() ?? - ) : ( - conversations.map((item) => { - return item.type === - V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P + const handleConversationScroll = ({ + clientHeight, + scrollHeight, + scrollTop, + }) => { + if ( + clientHeight !== undefined && + scrollHeight !== undefined && + scrollTop !== undefined + ) { + // 滚动到底部,加载更多会话 + const isScrolledToBottom = scrollHeight - clientHeight <= scrollTop + 10 + + if (isScrolledToBottom) { + handleLoadMoreConversations() + } + } + } + + const conversationRowRenderer = useCallback( + ({ index, key, style }) => { + const item = conversations[index] + + return ( +
+ {item.type === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P ? renderCustomP2pConversation?.({ conversation: item, onConversationItemClick, @@ -136,6 +156,9 @@ export const ConversationList: React.FC = ({ onStickTopChange={(isTop) => { onConversationItemStickTopChange(item, isTop) }} + onMuteChange={(mute) => { + onConversationItemMuteChange(item, mute) + }} conversationNameRenderer={renderConversationName?.({ conversation: item, })} @@ -146,8 +169,50 @@ export const ConversationList: React.FC = ({ conversation: item, })} /> - ) - }) + )} +
+ ) + }, + [ + conversations, + commonPrefix, + selectedConversation, + onConversationItemClick, + onConversationItemDeleteClick, + onConversationItemStickTopChange, + onConversationItemMuteChange, + renderCustomP2pConversation, + renderCustomTeamConversation, + renderConversationListEmpty, + renderP2pConversationAvatar, + renderTeamConversationAvatar, + renderConversationMsg, + renderConversationName, + prefix, + commonPrefix, + ] + ) + + return ( +
+ {loading ? ( + + ) : !conversations.length ? ( + renderConversationListEmpty?.() ?? + ) : ( + + {({ height, width }) => ( + + )} + )}
) 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 60b3afd..ce7db86 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 @@ -13,6 +13,7 @@ export interface GroupItemProps extends V2NIMConversationForUI { isSelected: boolean onStickTopChange: (isTop: boolean) => void onDeleteClick: () => void + onMuteChange: (mute: boolean) => void onItemClick: () => void avatarRenderer?: JSX.Element | null conversationNameRenderer?: JSX.Element | null @@ -24,7 +25,9 @@ export interface GroupItemProps extends V2NIMConversationForUI { export const GroupItem: FC = ({ onStickTopChange, onDeleteClick, + onMuteChange, conversationId, + mute = false, name, avatar, unreadCount, @@ -57,15 +60,15 @@ export const GroupItem: FC = ({ ), key: 'stickTop', }, - // { - // label: ext === '0' ? t('unmuteSessionText') : t('muteSessionText'), - // icon: isMute ? ( - // - // ) : ( - // - // ), - // key: 'muteSession', - // }, + { + label: mute ? t('unmuteSessionText') : t('muteSessionText'), + icon: mute ? ( + + ) : ( + + ), + key: 'muteConversation', + }, { label: t('deleteSessionText'), icon: , @@ -81,6 +84,9 @@ export const GroupItem: FC = ({ case 'stickTop': onStickTopChange(!stickTop) break + case 'muteConversation': + onMuteChange(!mute) + break case 'deleteConversation': onDeleteClick() break @@ -91,12 +97,12 @@ export const GroupItem: FC = ({ items={items} >
) - }, [stickTop, onStickTopChange, onDeleteClick, t]) + }, [stickTop, onStickTopChange, onDeleteClick, onMuteChange, t]) return ( = ({ account={teamId} avatar={avatar} count={isSelected ? 0 : unreadCount} + dot={isSelected ? false : mute && unreadCount > 0} /> ) } 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 d6fc9c8..ddc2c1c 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 @@ -8,10 +8,9 @@ import { ReadPercent, } from '../../common' 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' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface P2PItemProps extends V2NIMConversationForUI { isSelected: boolean @@ -121,11 +120,9 @@ export const P2PItem: FC = observer( {(msgReceiptTime ?? 0) - (lastMessage?.messageRefer.createTime ?? 0) >= 0 ? ( - + ) : ( - + )}
) : null diff --git a/react/src/YXUIKit/im-kit-ui/src/conversation/components/pinAIItem.tsx b/react/src/YXUIKit/im-kit-ui/src/conversation/components/pinAIItem.tsx index 5260c83..7d92fa9 100644 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/components/pinAIItem.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/conversation/components/pinAIItem.tsx @@ -5,10 +5,10 @@ import { useStateContext, useTranslation, } from '../../common' -import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMAIService' +import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMAIService' import { message } from 'antd' import { logger } from '../../utils' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface PinAIItemProps { aiUser: V2NIMAIUser diff --git a/react/src/YXUIKit/im-kit-ui/src/conversation/style/conversationItem.less b/react/src/YXUIKit/im-kit-ui/src/conversation/style/conversationItem.less deleted file mode 100644 index 0fa5f06..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/style/conversationItem.less +++ /dev/null @@ -1,91 +0,0 @@ -@import './theme.less'; - -@conversation-item-prefix-cls: ~'@{conversation-prefix}-item'; - -.@{conversation-item-prefix-cls} { - width: 100%; - display: flex; - padding: 12px; - background-color: @yx-primary-color; - position: relative; - cursor: pointer; - transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); - align-items: center; - - &-content { - display: flex; - flex-direction: column; - justify-content: space-between; - margin: 0 6px 0 10px; - flex: 1; - min-width: 0; - - &-name { - font-size: @yx-primary-font-size; - color: @yx-primary-text-color; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - - &-msg { - font-size: @yx-font-size-12; - color: @yx-text-color-2; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - display: flex; - align-items: center; - } - &-msg-body { - font-size: @yx-font-size-12; - color: @yx-text-color-2; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - } - &-mention { - font-size: @yx-font-size-12; - color: @yx-text-color-7; - } - &-read-status { - margin-right: 4px; - } - &-read-icon { - font-size: @yx-primary-font-size; - color: @yx-text-color-4 !important; - position: relative; - top: 1px; - } - } - - &-state { - display: flex; - flex-direction: column; - width: fit-content; - align-items: flex-end; - white-space: nowrap; - - &-date { - font-size: @yx-font-size-12; - color: @yx-text-color-2; - } - - &-mute { - color: @yx-icon-color-1; - height: 22px; - } - } - - &-top { - background-color: @yx-primary-color-hover; - } - - &-select { - background-color: @yx-primary-color-active; - } - - &:hover { - background-color: @yx-primary-color-active; - } -} diff --git a/react/src/YXUIKit/im-kit-ui/src/conversation/style/conversationList.less b/react/src/YXUIKit/im-kit-ui/src/conversation/style/conversationList.less deleted file mode 100644 index 2dab111..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/conversation/style/conversationList.less +++ /dev/null @@ -1,17 +0,0 @@ -@import './theme.less'; - -@conversation-list-prefix-cls: ~'@{conversation-prefix}-list'; - -.@{conversation-list-prefix-cls}-loading { - width: 100%; - display: flex; - align-items: center; - justify-content: center; -} - -.@{conversation-list-prefix-cls}-wrapper { - background-color: @yx-primary-color; - width: 100%; - height: 100%; - overflow-y: auto; -} diff --git a/react/src/YXUIKit/im-kit-ui/src/index.ts b/react/src/YXUIKit/im-kit-ui/src/index.ts index 7e89bef..0092459 100644 --- a/react/src/YXUIKit/im-kit-ui/src/index.ts +++ b/react/src/YXUIKit/im-kit-ui/src/index.ts @@ -42,12 +42,12 @@ import { import { ChatContainer, ChatMessageItem, ChatCollectionList } from './chat' import { AddContainer, SearchContainer } from './search' import RootStore from '@xkit-yx/im-store-v2' -import V2NIM from 'nim-web-sdk-ng' +import { NIM } from 'nim-web-sdk-ng/dist/esm/nim' import { LocalOptions } from '@xkit-yx/im-store-v2/dist/types/types' export class IMUIKit { get context(): { - nim: V2NIM + nim: NIM store: RootStore localOptions: LocalOptions } | void { @@ -91,7 +91,7 @@ export class IMUIKit { } getStateContext(): { - nim: V2NIM + nim: NIM store: RootStore localOptions: LocalOptions } | void { 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 ce310fd..6f8ed67 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 @@ -9,6 +9,7 @@ import AddFriendModal from './components/AddFriendModal' import JoinTeamModal from './components/JoinTeamModal' import packageJson from '../../../package.json' import { observer } from 'mobx-react' +// todo, v10.6.0 有静态方法可以获取版本号 import sdkPkg from 'nim-web-sdk-ng/package.json' export type PanelScene = 'addFriend' | 'joinTeam' | 'createTeam' 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 a30be10..bb7387d 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,9 +6,9 @@ import { CrudeAvatar, } from '../../../../common' import React, { useState } from 'react' -import { V2NIMUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMUserService' +import { V2NIMUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMUserService' import { observer } from 'mobx-react' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface AddFriendModalProps { visible: boolean 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 deleted file mode 100644 index b2af62f..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/search/add/components/CreateModal/index.tsx +++ /dev/null @@ -1,154 +0,0 @@ -import { Button, message, Modal, Input } from 'antd' -import React, { useState } from 'react' -import { - urls, - FriendSelectContainer, - GroupAvatarSelect, - useTranslation, - useStateContext, -} from '../../../../common' -import { V2NIMConst } from 'nim-web-sdk-ng' - -export interface CreateModalProps { - visible: boolean - onChat: (teamId: string) => void - onCancel: () => void - prefix?: string - commonPrefix?: string -} - -const CreateModal: React.FC = ({ - visible, - onChat, - onCancel, - prefix = 'search', - commonPrefix = 'common', -}) => { - const _prefix = `${prefix}-add-create-modal` - - const { t } = useTranslation() - - const { store } = useStateContext() - - const [selectedList, setSelectedList] = useState([]) - const [groupName, setGroupName] = useState('') - const [avatarUrl, setAvatarUrl] = useState( - urls[Math.floor(Math.random() * 5)] - ) - const [teamId, setTeamId] = useState('') - const [creating, setCreating] = useState(false) - - const handleCreate = async () => { - try { - setCreating(true) - const team = await store.teamStore.createTeamActive({ - accounts: selectedList, - avatar: avatarUrl, - name: groupName.trim(), - }) - message.success(t('createTeamSuccessText')) - setTeamId(team.teamId) - setCreating(false) - } catch (error) { - message.error(t('createTeamFailedText')) - setCreating(false) - } - } - - const handleChat = async () => { - if (teamId) { - await store.conversationStore.insertConversationActive( - V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_TEAM, - teamId - ) - onChat(teamId) - resetState() - } - } - - const handleCancel = () => { - onCancel() - resetState() - } - - const resetState = () => { - setSelectedList([]) - setGroupName('') - setAvatarUrl(urls[Math.floor(Math.random() * 5)]) - setTeamId('') - setCreating(false) - } - - const footer = ( -
- - {teamId ? ( - - ) : ( - - )} -
- ) - - return ( - -
-
{t('teamTitle')}
- { - setGroupName(e.target.value) - }} - /> -
-
-
- {t('teamAvatarText')} -
- { - setAvatarUrl(url) - }} - account={''} - prefix={commonPrefix} - /> -
- -
-
- {t('addTeamMemberText')} -
- { - setSelectedList(accounts) - }} - max={10} - selectedAccounts={selectedList} - prefix={commonPrefix} - /> -
-
- ) -} - -export default CreateModal 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 88d3f47..8b555d7 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,9 +6,9 @@ import { CrudeAvatar, } from '../../../../common' import React, { useState } from 'react' -import { V2NIMTeam } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' import { observer } from 'mobx-react' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface JoinTeamModalProps { visible: boolean diff --git a/react/src/YXUIKit/im-kit-ui/src/search/add/style/addModal.less b/react/src/YXUIKit/im-kit-ui/src/search/add/style/addModal.less index b4de25f..ff1e571 100644 --- a/react/src/YXUIKit/im-kit-ui/src/search/add/style/addModal.less +++ b/react/src/YXUIKit/im-kit-ui/src/search/add/style/addModal.less @@ -75,4 +75,4 @@ white-space: nowrap; } } -} +} \ No newline at end of file diff --git a/react/src/YXUIKit/im-kit-ui/src/search/add/style/createModal.less b/react/src/YXUIKit/im-kit-ui/src/search/add/style/createModal.less deleted file mode 100644 index 5888617..0000000 --- a/react/src/YXUIKit/im-kit-ui/src/search/add/style/createModal.less +++ /dev/null @@ -1,72 +0,0 @@ -@import '../style/theme.less'; - -@add-prefix-search-modal-cls: ~'@{add-prefix}-create-modal'; - -.@{add-prefix-search-modal-cls} { - .@{ant-prefix}-modal-content { - border-radius: @yx-border-radius-8; - - .@{ant-prefix}-modal-header { - border-radius: @yx-border-radius-8 @yx-border-radius-8 0 0; - } - } - - &-group-name { - margin-top: 12px; - display: flex; - align-items: center; - - &-content { - width: 80px; - height: 16px; - text-align: center; - font-family: 'PingFang SC'; - font-style: normal; - font-weight: 400; - font-size: @yx-primary-font-size; - line-height: 16px; - color: @yx-text-color-1; - } - - &-input { - width: 260px; - height: 32px; - margin-left: 16px; - } - } - - &-group-avatar { - margin-top: 24px; - display: flex; - align-items: center; - - &-content { - width: 85px; - height: 16px; - text-align: center; - font-family: 'PingFang SC'; - font-style: normal; - font-weight: 400; - font-size: @yx-primary-font-size; - line-height: 16px; - color: @yx-text-color-1; - margin-right: 16px; - } - } - - &-group-friendList { - width: 100%; - height: 320px; - margin-top: 24px; - - &-content { - height: 16px; - font-family: 'PingFang SC'; - font-style: normal; - font-weight: 400; - font-size: @yx-primary-font-size; - line-height: 16px; - color: @yx-text-color-1; - } - } -} 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 062490c..0f60e11 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 @@ -3,13 +3,14 @@ import { useTranslation, useEventTracking, useStateContext } from '../../common' import { SearchOutlined } from '@ant-design/icons' import SearchModal, { SectionListItem } from './components/SearchModal' import packageJson from '../../../package.json' -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 { V2NIMTeam } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' +import { V2NIMConversationType } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMConversationService' +import { V2NIMFriend } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMFriendService' +import { V2NIMUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMUserService' import { observer } from 'mobx-react' +// todo, v10.6.0 有静态方法可以获取版本号 import sdkPkg from 'nim-web-sdk-ng/package.json' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface SearchContainerProps { /** 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 dbf0777..01403d6 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,9 +2,9 @@ import React, { useState, useMemo, useCallback } from 'react' import { Modal } from 'antd' import { SearchInput, CrudeAvatar, useTranslation } from '../../../../common' import { CrudeAvatarProps } from '../../../../common/components/CrudeAvatar' -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' +import { V2NIMTeam } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' +import { V2NIMFriend } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMFriendService' +import { V2NIMUser } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMUserService' import { AutoSizer, List } from 'react-virtualized' export interface SearchItemProps extends CrudeAvatarProps { diff --git a/react/src/YXUIKit/im-kit-ui/src/utils.ts b/react/src/YXUIKit/im-kit-ui/src/utils.ts index 0b16501..7d80eb7 100644 --- a/react/src/YXUIKit/im-kit-ui/src/utils.ts +++ b/react/src/YXUIKit/im-kit-ui/src/utils.ts @@ -1,7 +1,10 @@ import { logDebug } from '@xkit-yx/utils' import moment from 'moment' import packageJson from '../package.json' -import { V2NIMMessage } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMMessageService' +import { + V2NIMMessage, + V2NIMMessageFileAttachment, +} from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMMessageService' export { logDebug } @@ -370,9 +373,12 @@ export const hasQueryParams = (url: string): boolean => { } export const getDownloadUrl = (msg: V2NIMMessage) => { - return hasQueryParams(msg.attachment.url) - ? `${msg.attachment.url}&download=${msg.messageClientId}${msg.attachment.ext}` - : `${msg.attachment.url}?download=${msg.messageClientId}${msg.attachment.ext}` + // todo 这个定义难过 + const attachment = msg.attachment as V2NIMMessageFileAttachment + + return hasQueryParams(attachment.url) + ? `${attachment.url}&download=${msg.messageClientId}${attachment.ext}` + : `${attachment.url}?download=${msg.messageClientId}${attachment.ext}` } export const getAIErrorMap = (t): { [key: number]: string } => { @@ -395,3 +401,37 @@ export const getAIErrorMap = (t): { [key: number]: string } => { 107336: t('tipAIMessageText'), } } + +// 当用户或者群聊没有头像时,用于根据account生成头像背景色 +export const getAvatarBackgroundColor = (account): string => { + const colorMap: { [key: number]: string } = { + 0: '#60CFA7', + 1: '#53C3F3', + 2: '#537FF4', + 3: '#854FE2', + 4: '#BE65D9', + 5: '#E9749D', + 6: '#F9B751', + } + // 将account转换为数字,取余数作为头像颜色索引 + const stringToNumber = (inputString) => { + let hash = 0 + + if (inputString.length === 0) { + return hash + } + + for (let i = 0; i < inputString.length; i++) { + const char = inputString.charCodeAt(i) + + hash = (hash << 5) - hash + char // 简单的加权算法 + hash = hash & hash // 将 hash 转换为 32 位整数 + } + + return Math.abs(hash) // 返回绝对值,确保为正数 + } + + const bgColorIndex = (stringToNumber(account) || 0) % 7 + + return colorMap[bgColorIndex] +}