From 8ad7471213a153f5f4cf86f4f490c6f1cdc92a4b Mon Sep 17 00:00:00 2001 From: yaoshun01 Date: Wed, 27 Nov 2024 16:49:35 +0800 Subject: [PATCH 1/2] v10.5.0 --- react/package.json | 6 +- react/src/YXUIKit/im-kit-ui/package.json | 2 +- .../components/ChatForwardModal/index.tsx | 50 +- .../ChatGroupTransferModal/index.tsx | 5 +- .../ChatMessageInput/useImgPaste.ts | 50 ++ .../components/ChatTeamMemberModal/index.tsx | 4 +- .../common/components/CommonIcon/index.tsx | 4 +- .../common/components/SelectModal/index.tsx | 5 +- .../im-kit-ui/src/common/locales/en.ts | 382 +++++++++++ .../AddFriendModal/index.stories.tsx | 24 + .../JoinTeamModal/index.stories.tsx | 24 + react/src/components/IMApp/IMApp.tsx | 445 +++++++++++++ .../IMApp/components/MenuOptions.tsx | 12 +- .../src/components/IMApp/components/Menus.tsx | 10 +- .../components/IMApp/components/Setting.tsx | 15 +- .../src/components/IMApp/components/call.tsx | 3 +- react/src/components/IMApp/index.less | 24 +- react/src/components/IMApp/index.tsx | 592 +++-------------- .../components/IMApp/locales/demo_locale.ts | 9 + react/src/components/IMApp/locales/en.ts | 75 ++- react/src/components/IMApp/locales/zh.ts | 75 ++- react/src/components/IMApp/util/index.ts | 30 +- vue/package.json | 2 +- vue/src/App.vue | 27 +- vue/src/components/IMApp/iconfont.css | 593 +++++++++++++++++- vue/src/components/IMApp/index.vue | 109 +++- 26 files changed, 1929 insertions(+), 648 deletions(-) create mode 100644 react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/useImgPaste.ts create mode 100644 react/src/YXUIKit/im-kit-ui/src/common/locales/en.ts create mode 100644 react/src/YXUIKit/im-kit-ui/src/search/add/components/AddFriendModal/index.stories.tsx create mode 100644 react/src/YXUIKit/im-kit-ui/src/search/add/components/JoinTeamModal/index.stories.tsx create mode 100644 react/src/components/IMApp/IMApp.tsx diff --git a/react/package.json b/react/package.json index 3a1851c..8cfd03b 100644 --- a/react/package.json +++ b/react/package.json @@ -2,14 +2,14 @@ "private": true, "author": "netease", "scripts": { - "dev": "umi dev", - "build": "umi build", + "dev": "NODE_OPTIONS=--openssl-legacy-provider && umi dev", + "build": "NODE_OPTIONS=--openssl-legacy-provider && umi dev", "start": "umi dev" }, "dependencies": { "@xkit-yx/call-kit": "^3.0.0", "@xkit-yx/call-kit-react-ui": "^0.6.0", - "@xkit-yx/im-kit-ui": "^10.x", + "@xkit-yx/im-kit-ui": "^10.5.1", "react-dom": "^16.8.0", "umi": "^3.5.40" }, diff --git a/react/src/YXUIKit/im-kit-ui/package.json b/react/src/YXUIKit/im-kit-ui/package.json index bff882a..c5a15c4 100644 --- a/react/src/YXUIKit/im-kit-ui/package.json +++ b/react/src/YXUIKit/im-kit-ui/package.json @@ -1,6 +1,6 @@ { "name": "@xkit-yx/im-kit-ui", - "version": "10.3.3", + "version": "10.5.1", "description": "云信即时通讯组件", "license": "MIT", "main": "lib/index.js", diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx index 7c025d3..1c2cecf 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx @@ -9,7 +9,7 @@ import { } from '../../../common' import { SelectModalItemProps } from '../../../common/components/SelectModal' import { V2NIMMessageForUI } 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 { groupByPy, logger } from '../../../utils' import { observer } from 'mobx-react' @@ -17,21 +17,6 @@ const localStorageKey = '__yx_im_recent_forward__' const localStorageMax = 5 const selectedMax = 9 -const getUniqueLatestItems = (items: ChatRecentForwardItem[]) => { - const map = new Map() - - items.forEach((item) => { - const exist = map.get(item.key) - - if (!exist || exist.time < item.time) { - map.set(item.key, item) - } - }) - return [...map.values()] - .sort((a, b) => b.time - a.time) - .slice(0, localStorageMax) -} - export type TabKey = 'conversation' | 'friend' | 'team' export interface ChatRecentForwardItem extends SelectModalItemProps { @@ -48,7 +33,7 @@ export interface ChatForwardModalProps { commonPrefix?: string } -const ChatForwardModal: React.FC = observer( +const ChatMessageForwardModal: React.FC = observer( ({ msg, visible, @@ -66,12 +51,9 @@ const ChatForwardModal: React.FC = observer( const myAccount = store.userStore.myUserInfo.accountId + // 用于获取最近转发列表 const finalStoreKey = `${localStorageKey}-${myAccount}` - useEffect(() => { - resetState() - }, [visible]) - const _prefix = `${prefix}-forward-modal` const friends = groupByPy( @@ -142,7 +124,7 @@ const ChatForwardModal: React.FC = observer( hide: tab !== 'conversation', })) as SelectModalItemProps[] - const datasource = [...conversations, ...friends, ...teams] + const dataSource = [...conversations, ...friends, ...teams] const recentForward = useMemo(() => { let res: ChatRecentForwardItem[] = [] @@ -277,6 +259,21 @@ const ChatForwardModal: React.FC = observer( ) }, [t, tab, _prefix]) + const getUniqueLatestItems = (items: ChatRecentForwardItem[]) => { + const map = new Map() + + items.forEach((item) => { + const exist = map.get(item.key) + + if (!exist || exist.time < item.time) { + map.set(item.key, item) + } + }) + return [...map.values()] + .sort((a, b) => b.time - a.time) + .slice(0, localStorageMax) + } + const handleCommentChange = (e: any) => { setComment(e.target.value) } @@ -325,12 +322,16 @@ const ChatForwardModal: React.FC = observer( setSelected(selected.filter((item) => item !== value.key)) } + useEffect(() => { + resetState() + }, [visible]) + return ( = observer( max={selectedMax} min={1} okText={t('sendBtnText')} + cancelText={t('cancelText')} showLeftTitle={false} rightTitle={t('sendToText')} bottomRenderer={ @@ -359,4 +361,4 @@ const ChatForwardModal: React.FC = observer( } ) -export default ChatForwardModal +export default ChatMessageForwardModal diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx index a7cbff9..c1a32bd 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx @@ -7,8 +7,8 @@ import { SelectModal, } from '../../../common' import { SelectModalItemProps } from '../../../common/components/SelectModal' -import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMTeamService' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMTeamMember } from 'nim-web-sdk-ng/dist/esm/nim/src/V2NIMTeamService' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' import { observer } from 'mobx-react' interface GroupActionModalProps { @@ -107,6 +107,7 @@ const GroupTransferModal: React.FC = observer( type="radio" min={1} okText={t('okText')} + cancelText={t('cancelText')} onOk={handleOk} onCancel={handleCancel} prefix={commonPrefix} diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/useImgPaste.ts b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/useImgPaste.ts new file mode 100644 index 0000000..f09695c --- /dev/null +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatMessageInput/useImgPaste.ts @@ -0,0 +1,50 @@ +import React, { useCallback, MutableRefObject } from 'react' + +interface useImgPasteProps { + focus?: boolean + textareaRef?: MutableRefObject + onSendImg: (file: File) => void +} + +export const useImgPaste = (props: useImgPasteProps) => { + const { textareaRef, onSendImg } = props + + const handleImgPaste = useCallback( + async (e: React.ClipboardEvent | any) => { + if (!(e.clipboardData && e.clipboardData.items)) { + return + // 右键粘贴 + } else if (e.clipboardData.files.length) { + if (e.clipboardData.files[0].type.match(/^image\//i)) { + return onSendImg && onSendImg(e.clipboardData.files[0]) + } + } + + const { types, items } = e.clipboardData + + types.find((type, index) => { + const item = items[index] + + switch (type) { + case 'Files': { + const file = item.getAsFile() + + if (item && item.kind === 'file' && item.type.match(/^image\//i)) { + onSendImg && onSendImg(file) + } + + return true + } + + default: + return true + } + }) + }, + [textareaRef, onSendImg] + ) + + return { + handleImgPaste, + } +} diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMemberModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMemberModal/index.tsx index 6186009..78c78be 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMemberModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamMemberModal/index.tsx @@ -8,7 +8,7 @@ import { } from '../../../common' import { observer } from 'mobx-react' import { SelectModalItemProps } from '../../../common/components/SelectModal' -import { V2NIMConst } from 'nim-web-sdk-ng' +import { V2NIMConst } from 'nim-web-sdk-ng/dist/esm/nim' export interface ChatTeamMemberModalProps { visible: boolean @@ -124,6 +124,8 @@ const ChatTeamMemberModal: React.FC = observer( type="checkbox" max={localOptions.teamManagerLimit} leftTitle={t('teamMemberText')} + cancelText={t('cancelText')} + okText={t('okText')} onOk={handleOk} onCancel={onCancel} prefix={commonPrefix} 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 1753dd6..f3f3473 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: [ - '//yx-web-nosdn.netease.im/sdk-release/yunxun-imweb-iconfont.js', - '//at.alicdn.com/t/c/font_3429868_qdz7pcft7cg.js', + 'https://yx-web-nosdn.netease.im/common/b38b4b232eb3933d8cb7ea16b2464f05/yunxun-imweb-iconfont.js', + 'https://at.alicdn.com/t/c/font_3429868_fwpfhemf2p.js', ], // 在 iconfont.cn 上生成 }) diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx index 1b61b8a..e4d0e3c 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx @@ -21,7 +21,7 @@ export interface SelectModalProps { onDelete?: (value: SelectModalItemProps) => void onOk: (data: SelectModalItemProps[]) => Promise onCancel: () => void - okText?: string + okText: string title?: string defaultValue?: string[] bottomRenderer?: React.ReactNode @@ -36,6 +36,7 @@ export interface SelectModalProps { rightTitle?: string closable?: boolean width?: number + cancelText: string prefix?: string } @@ -66,6 +67,7 @@ export const SelectModal: React.FC = ({ rightTitle, closable = true, width = 720, + cancelText, prefix = 'common', }) => { @@ -297,6 +299,7 @@ export const SelectModal: React.FC = ({ return ( + +export const Primary = () => { + const [visible, setVisible] = useState(true) + const props = { + visible, + onChat: (account: string) => { + console.log('去聊天', account) + }, + onCancel: () => setVisible(false), + prefix: 'search', + commonPrefix: 'common', + } + + return +} diff --git a/react/src/YXUIKit/im-kit-ui/src/search/add/components/JoinTeamModal/index.stories.tsx b/react/src/YXUIKit/im-kit-ui/src/search/add/components/JoinTeamModal/index.stories.tsx new file mode 100644 index 0000000..7fd052e --- /dev/null +++ b/react/src/YXUIKit/im-kit-ui/src/search/add/components/JoinTeamModal/index.stories.tsx @@ -0,0 +1,24 @@ +import React, { useState } from 'react' +import { ComponentMeta } from '@storybook/react' +import JoinTeamModal from './index' +import '../../style/addModal.less' + +export default { + title: 'search-kit/JoinTeamModal', + component: JoinTeamModal, +} as ComponentMeta + +export const Primary = () => { + const [visible, setVisible] = useState(true) + const props = { + visible, + onChat: (teamId: string) => { + console.log('去聊天', teamId) + }, + onCancel: () => setVisible(false), + prefix: 'search', + commonPrefix: 'common', + } + + return +} diff --git a/react/src/components/IMApp/IMApp.tsx b/react/src/components/IMApp/IMApp.tsx new file mode 100644 index 0000000..9caa56d --- /dev/null +++ b/react/src/components/IMApp/IMApp.tsx @@ -0,0 +1,445 @@ +import React, { + useEffect, + useMemo, + useRef, + useState, + useCallback, +} from "react"; +import { + ConversationContainer, // 会话列表组件 + ChatContainer, // 聊天(会话消息)组件 + ChatCollectionList, + AddContainer, // 搜索——添加按钮组件 + SearchContainer, // 搜索——搜索组件 + ContactListContainer, // 通讯录——通讯录导航组件 + ContactInfoContainer, // 通讯录——通讯录详情组件,包含好友列表、群组列表以及黑名单列表 + MyAvatarContainer, + useStateContext, + ComplexAvatarContainer, + Utils, +} from "@xkit-yx/im-kit-ui/src"; +import { Badge, Button, Popover, message, Dropdown, Menu } from "antd"; +import classNames from "classnames"; +import { observer } from "mobx-react"; +import "@xkit-yx/im-kit-ui/src/style"; +import "antd/es/badge/style"; +import "./iconfont.css"; +import "./index.less"; +// 左下角菜单组件 +import SettingModal from "./components/Setting"; +import Menus from "./components/Menus"; +// 呼叫组件 +import { + CallViewProvider, + CallViewProviderRef, + //@ts-ignore +} from "@xkit-yx/call-kit-react-ui"; +import "@xkit-yx/call-kit-react-ui/es/style"; +import Calling from "./components/call"; +//demo国际化函数 +import { convertSecondsToTime, g2StatusMap, t } from "./util"; +import { DeleteOutlined } from "@ant-design/icons"; +import { + pauseAllAudio, + pauseAllVideo, +} from "@xkit-yx/im-kit-ui/src/common/components/CommonParseSession"; +import { V2NIMConst } from "nim-web-sdk-ng/dist/esm/nim"; + +interface IMAppProps { + appkey: string; //传入您的App Key + account: string; // 传入您的云信IM账号 + onLogout?: () => void; + locale: "zh" | "en"; + addFriendNeedVerify: boolean; + p2pMsgReceiptVisible: boolean; + teamMsgReceiptVisible: boolean; + needMention: boolean; + teamManagerVisible: boolean; +} +const IM: React.FC = observer((props) => { + const { + onLogout, + locale, + addFriendNeedVerify, + appkey, + p2pMsgReceiptVisible, + teamMsgReceiptVisible, + needMention, + teamManagerVisible, + } = props; + const callViewProviderRef = useRef(null); + const [model, setModel] = useState<"chat" | "contact" | "collection">("chat"); + const [isSettingModalOpen, setIsSettingModalOpen] = useState(false); + // 是否显示呼叫弹窗 + const [callingVisible, setCallingVisible] = useState(false); + // IMUIKit store 与 nim sdk 实例 + const { store, nim } = useStateContext(); + + // const conversationId = store.uiStore.selectedConversation + const conversationType = nim.V2NIMConversationIdUtil.parseConversationType( + store.uiStore.selectedConversation + ); + const receiverId = nim.V2NIMConversationIdUtil.parseConversationTargetId( + store.uiStore.selectedConversation + ); + + const { relation } = store.uiStore.getRelation(receiverId); + + const messageActionDropdownContainerRef = useRef(null); + + const handleSettingCancel = () => { + setIsSettingModalOpen(false); + }; + + const openSettingModal = () => { + setIsSettingModalOpen(true); + }; + + // 发起呼叫 + const handleCall = useCallback( + async (callType) => { + try { + await callViewProviderRef.current?.call?.({ + accId: receiverId, + callType, + }); + setCallingVisible(false); + } catch (error) { + switch (error.code) { + // 忙线 + case "105": + message.error(t("inCallText")); + break; + // 无网络 + case "Error_Internet_Disconnected": + message.error(t("networkDisconnectText")); + break; + default: + // 处理其他错误 + message.error(t("callFailed")); + break; + } + } + }, + [receiverId] + ); + + // 重新渲染发送按钮,增加呼叫按钮 + const actions = useMemo( + () => [ + { + action: "emoji", + visible: true, + }, + { + action: "sendImg", + visible: true, + }, + { + action: "sendFile", + visible: true, + }, + { action: "aiTranslate" }, + { + action: "calling", + visible: + conversationType === + V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P && + relation !== "ai", + render: () => { + return ( + + ); + }, + }, + { + action: "sendMsg", + visible: true, + }, + ], + [callingVisible, handleCall, conversationType, relation] + ); + + // 根据msg.type 自定义渲染话单消息,当msg.type 为 g2 代表的是话单消息,使用renderP2pCustomMessage进行自定义渲染 + const renderP2pCustomMessage = useCallback( + (options) => { + const msg = options.msg; + + // msg.type 为 g2 代表的是话单消息 renderP2pCustomMessage 返回 null 就会按照组件默认的逻辑进行展示消息 + if ( + msg.messageType !== V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL + ) { + return null; + } + + const attach = msg.attachment as any; + const raw = JSON.parse(attach?.raw || "{}"); + const duration = raw.durations[0]?.duration; + const status = raw.status; + const type = raw.type; + const icon = type == 1 ? "icon-yuyin8" : "icon-shipin8"; + const myAccount = store.userStore.myUserInfo.accountId; + //判断当前消息是发出的消息还是接收的消息 + const isSelf = msg.senderId === myAccount; + const account = isSelf ? myAccount : receiverId; + const deleteG2Message = (msg) => { + store.msgStore.deleteMsgActive([msg]); + }; + + return ( +
+ + + messageActionDropdownContainerRef.current || triggerNode + } + overlay={ + deleteG2Message(msg)} + items={[ + { + label: t("deleteText"), + key: "delete", + icon: , + }, + ]} + /> + } + > +
+
+ {store.uiStore.getAppellation({ account })} +
+ +
handleCall(type.toString())} + > + + {g2StatusMap[status]} + {duration ? ( + + {convertSecondsToTime(duration)} + + ) : null} +
+
{Utils.formatDate(msg.createTime)}
+
+ +
+ ); + }, + [ + handleCall, + receiverId, + store.uiStore, + store.msgStore, + store.userStore.myUserInfo.accountId, + ] + ); + + const goChat = useCallback(() => { + setModel("chat"); + }, []); + + const afterAcceptApplyFriend = useCallback( + (data) => { + const textMsg = nim.V2NIMMessageCreator.createTextMessage( + t("passFriendAskText") + ); + + store.msgStore + .sendMessageActive({ + msg: textMsg, + conversationId: nim.V2NIMConversationIdUtil.p2pConversationId( + data.operatorAccountId + ), + }) + .then(() => { + setModel("chat"); + }); + }, + [nim.V2NIMConversationIdUtil, nim.V2NIMMessageCreator, store.msgStore] + ); + + useEffect(() => { + if (callViewProviderRef.current?.neCall) { + //注册呼叫结束事件监听 + callViewProviderRef.current?.neCall?.on("onRecordSend", (options) => { + //@ts-ignore + store.msgStore.addMsg(options.conversationId, [options]); + document.getElementById(options.messageClientId)?.scrollIntoView({ + block: "nearest", // 滚动到目标元素的最近可见位置 + inline: "nearest", // 避免水平方向的滚动 + }); + }); + // 设置呼叫超时时间 + callViewProviderRef.current?.neCall?.setTimeout(30); + // 接通成功事件 + callViewProviderRef.current?.neCall?.on("onCallConnected", () => { + // 暂停音视频消息的播放 + pauseAllAudio(); + pauseAllVideo(); + }); + } + }, [callViewProviderRef.current?.neCall, store.msgStore]); + + const renderContent = useCallback(() => { + return ( + <> +
+
+ +
+
+ +
+
+
+
+
+ +
+ +
setModel("chat")} + > + +
{t("session")}
+
+
+ +
setModel("collection")} + > + +
{t("collectionText")}
+
+
+ +
setModel("contact")} + > + +
{t("addressText")}
+
+
+ { + onLogout && onLogout(); + store.resetState(); + }} + locale={locale} + openSettingModal={openSettingModal} + /> +
+ {/* 若您复制IMUIKit demo至您的工程,SettingModal相关代码可以删除 */} + + {model === "chat" && ( +
+
{t("securityTipText")}
+
+
+ +
+
+ +
+
+
+ )} + {model === "contact" && ( +
+
{t("securityTipText")}
+
+
+ +
+
+ +
+
+
+ )} + {model === "collection" && } +
+ + ); + }, [ + actions, + addFriendNeedVerify, + isSettingModalOpen, + locale, + model, + onLogout, + p2pMsgReceiptVisible, + teamMsgReceiptVisible, + renderP2pCustomMessage, + store.sysMsgStore, + needMention, + teamManagerVisible, + afterAcceptApplyFriend, + goChat, + ]); + + return ( + + {renderContent()} + + ); +}); + +export default IM; diff --git a/react/src/components/IMApp/components/MenuOptions.tsx b/react/src/components/IMApp/components/MenuOptions.tsx index fd72239..6e60bda 100644 --- a/react/src/components/IMApp/components/MenuOptions.tsx +++ b/react/src/components/IMApp/components/MenuOptions.tsx @@ -2,10 +2,10 @@ import React, { FC } from 'react' import classNames from 'classnames' import { Modal } from 'antd' import { t } from '../util' + interface IProps { onLogout?: () => void locale: 'zh' | 'en' - changeLanguage?: (value: 'zh' | 'en') => void openSettingModal: () => void resetMenuUI: () => void menuVisible: boolean @@ -16,7 +16,6 @@ interface IProps { const Menu: FC = ({ onLogout, locale, - changeLanguage, openSettingModal, resetMenuUI, menuVisible, @@ -32,6 +31,7 @@ const Menu: FC = ({ }, }) } + return menuVisible ? (
= ({ active: locale === 'zh', })} onClick={() => { - changeLanguage?.('zh') - resetMenuUI() + sessionStorage.setItem('languageType', 'zh') + window.location.reload() }} > 中文 @@ -81,8 +81,8 @@ const Menu: FC = ({ active: locale === 'en', })} onClick={() => { - changeLanguage?.('en') - resetMenuUI() + sessionStorage.setItem('languageType', 'en') + window.location.reload() }} > English diff --git a/react/src/components/IMApp/components/Menus.tsx b/react/src/components/IMApp/components/Menus.tsx index 5bf45c8..4efb00b 100644 --- a/react/src/components/IMApp/components/Menus.tsx +++ b/react/src/components/IMApp/components/Menus.tsx @@ -1,18 +1,13 @@ import React, { FC, useState } from 'react' import MenuOptions from './MenuOptions' import '../index.less' + interface IProps { onLogout?: () => void locale: 'zh' | 'en' - changeLanguage?: (value: 'zh' | 'en') => void openSettingModal: () => void } -const MenuIcon: FC = ({ - onLogout, - locale, - changeLanguage, - openSettingModal, -}) => { +const MenuIcon: FC = ({ onLogout, locale, openSettingModal }) => { const [menuVisible, setMenuVisible] = useState(false) const [subMenuVisible, setSubMenuVisible] = useState(false) const resetMenuUI = () => { @@ -34,7 +29,6 @@ const MenuIcon: FC = ({ void @@ -31,20 +32,6 @@ const SettingModal: FC = ({ width={locale === 'zh' ? 420 : 460} >
- {/* - void } diff --git a/react/src/components/IMApp/index.less b/react/src/components/IMApp/index.less index 7bee284..e875b4c 100644 --- a/react/src/components/IMApp/index.less +++ b/react/src/components/IMApp/index.less @@ -1,7 +1,7 @@ -body{ - background: #d8dee5; -} .im-app-example { + width: 100vw; + height: 100vh; + background: #d8dee5; .container { width: 1070px; height: 670px; @@ -12,6 +12,8 @@ body{ left: 50%; transform: translate(-50%, -50%); background: #fff; + border-radius: 6px; + overflow: hidden; } .header { @@ -38,7 +40,9 @@ body{ } .left { + flex: 0 0 60px; width: 60px; + box-sizing: border-box; border-right: 1px solid #e8e8e8; display: flex; flex-direction: column; @@ -76,6 +80,7 @@ body{ font-size: 22px; color: rgba(0, 0, 0, 0.6); height: 45px; + width: 36px; display: flex; flex-direction: column; justify-content: space-between; @@ -114,7 +119,7 @@ body{ width: 300px; height: 102px; left: 10px; - top: 510px; + top: 490px; padding: 4px 4px 4px 40px; cursor: pointer; @@ -193,9 +198,20 @@ body{ .right { flex: 1; + } + .right-content-wrap { + height: 574px; display: flex; + box-sizing: border-box; } + .security-tip { + background: #fff5e1; + height: 36px; + text-align: center; + line-height: 36px; + color: #eb9718; + } .right-list { width: 200px; border-right: 1px solid #e8e8e8; diff --git a/react/src/components/IMApp/index.tsx b/react/src/components/IMApp/index.tsx index fa6a328..70d95d2 100644 --- a/react/src/components/IMApp/index.tsx +++ b/react/src/components/IMApp/index.tsx @@ -1,481 +1,58 @@ -import React, { useEffect, useMemo, useRef, useState, useCallback } from 'react' +import React, { useEffect, useMemo, useState, useCallback } from "react"; import { Provider, // 全局上下文 - ConversationContainer, // 会话列表组件 - ChatContainer, // 聊天(会话消息)组件 - ChatCollectionList, - AddContainer, // 搜索——添加按钮组件 - SearchContainer, // 搜索——搜索组件 - ContactListContainer, // 通讯录——通讯录导航组件 - ContactInfoContainer, // 通讯录——通讯录详情组件,包含好友列表、群组列表以及黑名单列表 - MyAvatarContainer, - useStateContext, - ComplexAvatarContainer, - Utils, -} from '@xkit-yx/im-kit-ui/src' -import { - ConfigProvider, - Badge, - Button, - Popover, - message, - Dropdown, - Menu, -} from 'antd' +} from "@xkit-yx/im-kit-ui/src"; +import { ConfigProvider } from "antd"; //antd 国际化 -import zhCN from 'antd/es/locale/zh_CN' -import enUS from 'antd/es/locale/en_US' +import zhCN from "antd/es/locale/zh_CN"; +import enUS from "antd/es/locale/en_US"; //UIKit 国际化 -import en from './locales/en' -import zh from './locales/zh' -import classNames from 'classnames' -import { observer } from 'mobx-react' -import '@xkit-yx/im-kit-ui/src/style' -import 'antd/es/badge/style' -import './iconfont.css' -import './index.less' -// 左下角菜单组件 -import SettingModal from './components/Setting' -import Menus from './components/Menus' -// 呼叫组件 -import { - CallViewProvider, - CallViewProviderRef, -} from '@xkit-yx/call-kit-react-ui' -import '@xkit-yx/call-kit-react-ui/es/style' -import Calling from './components/call' -//demo国际化函数 -import { convertSecondsToTime, g2StatusMap, t } from './util' -import { DeleteOutlined } from '@ant-design/icons' -import { - pauseAllAudio, - pauseAllVideo, -} from '@xkit-yx/im-kit-ui/src/common/components/CommonParseSession' -import { LocalOptions } from '@xkit-yx/im-store-v2/dist/types/types' -import V2NIM, { V2NIMConst } from 'nim-web-sdk-ng' -import { V2NIMAIUser } from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/V2NIMAIService' +import en from "./locales/en"; +import zh from "./locales/zh"; +import "@xkit-yx/im-kit-ui/src/style"; +import "antd/es/badge/style"; +import "./iconfont.css"; +import "./index.less"; +import { LocalOptions } from "@xkit-yx/im-store-v2/dist/types/types"; +import V2NIM from "nim-web-sdk-ng"; +import { V2NIMConst } from "nim-web-sdk-ng/dist/esm/nim"; +import { V2NIMAIUser } from "nim-web-sdk-ng/dist/esm/nim/src/V2NIMAIService"; import { NIMInitializeOptions, NIMOtherOptions, -} from 'nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/NIMInterface' -import { RenderP2pCustomMessageOptions } from '@xkit-yx/im-kit-ui/es/chat/components/ChatP2pMessageList' +} from "nim-web-sdk-ng/dist/v2/NIM_BROWSER_SDK/NIMInterface"; -interface IMContainerProps { - appkey: string //传入您的App Key - account: string // 传入您的云信IM账号 - token: string // 传入您的Token - initOptions?: NIMInitializeOptions - otherOptions?: NIMOtherOptions - onLogout?: () => void - changeLanguage?: (value: 'zh' | 'en') => void -} +import IMApp from "./IMApp"; -interface IMAppProps { - appkey: string //传入您的App Key - account: string // 传入您的云信IM账号 - onLogout?: () => void - locale: 'zh' | 'en' - changeLanguage?: (value: 'zh' | 'en') => void - addFriendNeedVerify: boolean - p2pMsgReceiptVisible: boolean - teamMsgReceiptVisible: boolean - needMention: boolean - teamManagerVisible: boolean +interface IMContainerProps { + appkey: string; //传入您的App Key + account: string; // 传入您的云信IM账号 + token: string; // 传入您的Token + initOptions?: NIMInitializeOptions; + otherOptions?: NIMOtherOptions; + onLogout?: () => void; + changeLanguage?: (value: "zh" | "en") => void; } -const IMApp: React.FC = observer((props) => { - const { - onLogout, - locale, - changeLanguage, - addFriendNeedVerify, - appkey, - p2pMsgReceiptVisible, - teamMsgReceiptVisible, - needMention, - teamManagerVisible, - } = props - const callViewProviderRef = useRef(null) - const [model, setModel] = useState<'chat' | 'contact' | 'collection'>('chat') - const [isSettingModalOpen, setIsSettingModalOpen] = useState(false) - // 是否显示呼叫弹窗 - const [callingVisible, setCallingVisible] = useState(false) - // IMUIKit store 与 nim sdk 实例 - const { store, nim } = useStateContext() - - // const conversationId = store.uiStore.selectedConversation - const conversationType = nim.V2NIMConversationIdUtil.parseConversationType( - store.uiStore.selectedConversation - ) - const receiverId = nim.V2NIMConversationIdUtil.parseConversationTargetId( - store.uiStore.selectedConversation - ) - - const { relation } = store.uiStore.getRelation(receiverId) - - const messageActionDropdownContainerRef = useRef(null) - - const handleSettingCancel = () => { - setIsSettingModalOpen(false) - } - - const openSettingModal = () => { - setIsSettingModalOpen(true) - } - - useEffect(() => { - if (callViewProviderRef.current?.neCall) { - //注册呼叫结束事件监听 - callViewProviderRef.current?.neCall?.on('onRecordSend', (options) => { - store.msgStore.addMsg(options.conversationId, [options]) - document.getElementById(options.messageClientId)?.scrollIntoView() - }) - // 设置呼叫超时时间 - callViewProviderRef.current?.neCall?.setTimeout(30) - // 接通成功事件 - callViewProviderRef.current?.neCall?.on('onCallConnected', () => { - // 暂停音视频消息的播放 - pauseAllAudio() - pauseAllVideo() - }) - } - }, [callViewProviderRef.current?.neCall, store.msgStore]) - - // 发起呼叫 - const handleCall = useCallback( - async (callType) => { - try { - await callViewProviderRef.current?.call?.({ - accId: receiverId, - callType, - }) - setCallingVisible(false) - } catch (error) { - switch (error.code) { - // 忙线 - case '105': - message.error(t('inCallText')) - break - // 无网络 - case 'Error_Internet_Disconnected': - message.error(t('networkDisconnectText')) - break - default: - // 处理其他错误 - message.error(t('callFailed')) - break - } - } - }, - [receiverId] - ) - - // 重新渲染发送按钮,增加呼叫按钮 - const actions = useMemo( - () => [ - { - action: 'emoji', - visible: true, - }, - { - action: 'sendImg', - visible: true, - }, - { - action: 'sendFile', - visible: true, - }, - { action: 'aiTranslate' }, - { - action: 'calling', - visible: - conversationType === - V2NIMConst.V2NIMConversationType.V2NIM_CONVERSATION_TYPE_P2P && - relation !== 'ai', - render: () => { - return ( - - ) - }, - }, - { - action: 'sendMsg', - visible: true, - }, - ], - [callingVisible, handleCall, conversationType, relation] - ) - - // 根据msg.type 自定义渲染话单消息,当msg.type 为 g2 代表的是话单消息,使用renderP2pCustomMessage进行自定义渲染 - const renderP2pCustomMessage = useCallback( - (options: RenderP2pCustomMessageOptions) => { - const msg = options.msg - - // msg.type 为 g2 代表的是话单消息 renderP2pCustomMessage 返回 null 就会按照组件默认的逻辑进行展示消息 - if ( - msg.messageType !== V2NIMConst.V2NIMMessageType.V2NIM_MESSAGE_TYPE_CALL - ) { - return null - } - - const attach = msg.attachment as any - const raw = JSON.parse(attach?.raw || '{}') - const duration = raw.durations[0]?.duration - const status = raw.status - const type = raw.type - const icon = type == 1 ? 'icon-yuyin8' : 'icon-shipin8' - const myAccount = store.userStore.myUserInfo.accountId - //判断当前消息是发出的消息还是接收的消息 - const isSelf = msg.senderId === myAccount - const account = isSelf ? myAccount : receiverId - const deleteG2Message = (msg) => { - console.log('msg:', msg) - store.msgStore.deleteMsgActive([msg]) - } - - return ( -
- - - messageActionDropdownContainerRef.current || triggerNode - } - overlay={ - deleteG2Message(msg)} - items={[ - { - label: t('deleteText'), - key: 'delete', - icon: , - }, - ]} - /> - } - > -
-
- {store.uiStore.getAppellation({ account })} -
- -
handleCall(type.toString())} - > - - {g2StatusMap[status]} - {duration ? ( - - {convertSecondsToTime(duration)} - - ) : null} -
-
{Utils.formatDate(msg.createTime)}
-
- -
- ) - }, - [ - handleCall, - receiverId, - store.uiStore, - store.msgStore, - store.userStore.myUserInfo.accountId, - ] - ) - - const goChat = useCallback(() => { - setModel('chat') - }, []) - - const afterAcceptApplyFriend = useCallback( - (data) => { - const textMsg = nim.V2NIMMessageCreator.createTextMessage( - t('passFriendAskText') - ) - - store.msgStore - .sendMessageActive({ - msg: textMsg, - conversationId: nim.V2NIMConversationIdUtil.p2pConversationId( - data.operatorAccountId - ), - }) - .then(() => { - setModel('chat') - }) - }, - [nim.V2NIMConversationIdUtil, nim.V2NIMMessageCreator, store.msgStore] - ) - - const renderContent = useCallback(() => { - return ( - <> -
-
- -
-
- -
-
-
-
-
- -
- -
setModel('chat')} - > - -
{t('session')}
-
-
- -
setModel('collection')} - > - -
{t('collectionText')}
-
-
- -
setModel('contact')} - > - -
{t('addressText')}
-
-
- -
- {/* 若您复制IMUIKit demo至您的工程,SettingModal相关代码可以删除 */} - - {model === 'chat' && ( -
-
- -
-
- -
-
- )} - {model === 'contact' && ( -
-
- -
-
- -
-
- )} - {model === 'collection' && } -
- - ) - }, [ - actions, - addFriendNeedVerify, - changeLanguage, - isSettingModalOpen, - locale, - model, - onLogout, - p2pMsgReceiptVisible, - teamMsgReceiptVisible, - renderP2pCustomMessage, - store.sysMsgStore, - needMention, - teamManagerVisible, - afterAcceptApplyFriend, - goChat, - ]) - - return ( - - {renderContent()} - - ) -}) - const IMAppContainer: React.FC = (props) => { - const { appkey, account, token, initOptions, otherOptions, onLogout } = props + const { appkey, account, token, initOptions, otherOptions, onLogout } = props; // 国际化语言类型 - const [curLanguage, setCurLanguage] = useState<'zh' | 'en'>('zh') + const [curLanguage, setCurLanguage] = useState<"zh" | "en">("zh"); // 添加好友是否需要验证 - const [addFriendNeedVerify, setAddFriendNeedVerify] = useState(true) + const [addFriendNeedVerify, setAddFriendNeedVerify] = useState(true); //单聊消息是否显示已读未读 - const [p2pMsgReceiptVisible, setP2pMsgReceiptVisible] = - useState(true) + const [p2pMsgReceiptVisible, setP2pMsgReceiptVisible] = useState( + true + ); //群聊消息是否显示已读未读 - const [teamMsgReceiptVisible, setTeamMsgReceiptVisible] = - useState(true) + const [teamMsgReceiptVisible, setTeamMsgReceiptVisible] = useState( + true + ); // 是否需要@消息 - const [needMention, setNeedMention] = useState(true) + const [needMention, setNeedMention] = useState(true); // 是否开启群管理员功能 - const [teamManagerVisible, setTeamManagerVisible] = useState(true) - const languageMap = useMemo(() => ({ zh, en }), []) + const [teamManagerVisible, setTeamManagerVisible] = useState(true); + const languageMap = useMemo(() => ({ zh, en }), []); // 本地默认行为参数 const localOptions: Partial = useMemo(() => { @@ -506,113 +83,119 @@ const IMAppContainer: React.FC = (props) => { */ getAISearchUser: (users: V2NIMAIUser[]): V2NIMAIUser | void => { // demo 根据 accid 匹配,具体值根据业务后台配置的来 - return users.find((item) => item.accountId === 'search') + return users.find((item) => item.accountId === "search"); }, /** * 注册 AI 翻译数字人 */ getAITranslateUser: (users: V2NIMAIUser[]): V2NIMAIUser | void => { // demo 根据 accid 匹配,具体值根据业务后台配置的来 - return users.find((item) => item.accountId === 'translation') + return users.find((item) => item.accountId === "translation"); }, /** * 注册 AI 翻译语言 */ getAITranslateLangs: (users: V2NIMAIUser[]): string[] => { - return ['英语', '日语', '韩语', '俄语', '法语', '德语'] + return ["英语", "日语", "韩语", "俄语", "法语", "德语"]; }, }, - } + }; }, [ addFriendNeedVerify, needMention, p2pMsgReceiptVisible, teamManagerVisible, teamMsgReceiptVisible, - ]) - - const changeLanguage = useCallback((value: 'zh' | 'en') => { - setCurLanguage(value) - sessionStorage.setItem('languageType', value) - window.location.reload() - }, []) + ]); + // 此处是为了便于demo体验,实际开发中请根据业务需求配置 useEffect(() => { - const _languageType = sessionStorage.getItem('languageType') as 'zh' | 'en' - const _addFriendNeedVerify = sessionStorage.getItem('addFriendNeedVerify') - const _p2pMsgReceiptVisible = sessionStorage.getItem('p2pMsgReceiptVisible') + const _languageType = sessionStorage.getItem("languageType") as "zh" | "en"; + const _addFriendNeedVerify = sessionStorage.getItem("addFriendNeedVerify"); + const _p2pMsgReceiptVisible = sessionStorage.getItem( + "p2pMsgReceiptVisible" + ); const _teamMsgReceiptVisible = sessionStorage.getItem( - 'teamMsgReceiptVisible' - ) - const _needMention = sessionStorage.getItem('needMention') - const _teamManagerVisible = sessionStorage.getItem('teamManagerVisible') + "teamMsgReceiptVisible" + ); + const _needMention = sessionStorage.getItem("needMention"); + const _teamManagerVisible = sessionStorage.getItem("teamManagerVisible"); - setCurLanguage(_languageType || 'zh') + setCurLanguage(_languageType || "zh"); if (_p2pMsgReceiptVisible) { - setP2pMsgReceiptVisible(_p2pMsgReceiptVisible === 'true') + setP2pMsgReceiptVisible(_p2pMsgReceiptVisible === "true"); } if (_teamMsgReceiptVisible) { - setTeamMsgReceiptVisible(_teamMsgReceiptVisible === 'true') + setTeamMsgReceiptVisible(_teamMsgReceiptVisible === "true"); } if (_addFriendNeedVerify) { - setAddFriendNeedVerify(_addFriendNeedVerify === 'true') + setAddFriendNeedVerify(_addFriendNeedVerify === "true"); } if (_needMention) { - setNeedMention(_needMention === 'true') + setNeedMention(_needMention === "true"); } if (_teamManagerVisible) { - setTeamManagerVisible(_teamManagerVisible === 'true') + setTeamManagerVisible(_teamManagerVisible === "true"); } - }, []) + }, []); const nim = useMemo(() => { console.log( - 'V2NIM.getInstance: ', + "V2NIM.getInstance: ", { appkey, - debugLevel: 'debug', - apiVersion: 'v2', + debugLevel: "debug", + apiVersion: "v2", ...initOptions, }, otherOptions - ) + ); const nim = V2NIM.getInstance( { appkey, - debugLevel: 'debug', - apiVersion: 'v2', + debugLevel: "debug", + apiVersion: "v2", ...initOptions, }, otherOptions - ) + ); - return nim - }, [appkey, initOptions, otherOptions]) + return nim; + }, [appkey, initOptions, otherOptions]); useEffect(() => { - if (account && token) { + if ( + account && + token && + // @ts-ignore + nim.V2NIMLoginService.getLoginStatus() === + V2NIMConst.V2NIMLoginStatus.V2NIM_LOGIN_STATUS_LOGOUT + ) { nim.V2NIMLoginService.login(account, token, { retryCount: 5, - }) + }).catch((err) => { + console.log("登录失败的回调", err); + }); } - }, [account, token, nim.V2NIMLoginService]) + }, [account, token, nim.V2NIMLoginService]); const handleLogout = useCallback(async () => { - await nim.V2NIMLoginService.logout() - onLogout?.() - }, [onLogout, nim.V2NIMLoginService]) + await nim.V2NIMLoginService.logout(); + onLogout?.(); + }, [onLogout, nim.V2NIMLoginService]); return ( - +
@@ -621,7 +204,6 @@ const IMAppContainer: React.FC = (props) => { appkey={appkey} account={account} locale={curLanguage} - changeLanguage={changeLanguage} addFriendNeedVerify={addFriendNeedVerify} p2pMsgReceiptVisible={p2pMsgReceiptVisible} teamMsgReceiptVisible={teamMsgReceiptVisible} @@ -632,7 +214,7 @@ const IMAppContainer: React.FC = (props) => {
- ) -} + ); +}; -export default IMAppContainer +export default IMAppContainer; diff --git a/react/src/components/IMApp/locales/demo_locale.ts b/react/src/components/IMApp/locales/demo_locale.ts index 183284c..0306092 100644 --- a/react/src/components/IMApp/locales/demo_locale.ts +++ b/react/src/components/IMApp/locales/demo_locale.ts @@ -28,6 +28,11 @@ export const demo_zh = { teamManagerEnableText: '是否开启群管理员功能', deleteText: '删除', collectionText: '收藏', + languageText: '语言', + zhText: '中文', + enText: 'English', + securityTipText: + '仅用于体验云信IM 产品功能,请勿轻信汇款、中奖等涉及钱款的信息,勿轻易拨打陌生电话,谨防上当受骗。', } export const demo_en = { @@ -61,4 +66,8 @@ export const demo_en = { teamManagerEnableText: 'Whether or not team manager is needed', deleteText: 'delete', collectionText: 'collection', + zhText: '中文', + enText: 'English', + securityTipText: + 'Only for testing the Netease IM product function, please do not believe in any information about money transfer, lottery winning and so on. Do not easily call strangers and be careful to get cheated.', } diff --git a/react/src/components/IMApp/locales/en.ts b/react/src/components/IMApp/locales/en.ts index a82422b..cd036e8 100644 --- a/react/src/components/IMApp/locales/en.ts +++ b/react/src/components/IMApp/locales/en.ts @@ -75,6 +75,7 @@ const LocaleConfig = { updateTeamAvatar: 'Updated Group Avatar', updateTeamName: 'Updated Group Name to', updateTeamIntro: 'Updated Group Introduction', + voiceToTextFailedText: 'Speech to text failed', updateTeamInviteMode: 'Updated Group Permission "Invite Others" to', updateTeamUpdateTeamMode: 'Updated Group Permission "Edit Group Info" to', updateAllowAt: 'Updated "@Everyone Permission" to', @@ -94,6 +95,7 @@ const LocaleConfig = { noPermission: 'You Do Not Have Permission', unreadText: 'Unread', readText: 'Read', + voiceToText: 'Speech to text', allReadText: 'All Read', amap: 'Amap', txmap: 'Tencent Map', @@ -184,6 +186,7 @@ const LocaleConfig = { collection: 'Collect', collectionSuccess: 'Collected', collectionFailed: 'Collect Failed', + msgRecallTimeErrorText: 'The time has passed and cannot be recalled', getCollectionFailed: 'Failed to Retrieve Collection List', removeCollectionSuccess: 'Delete Collection Successful', removeCollectionFailed: 'Delete Collection Failed', @@ -234,7 +237,7 @@ const LocaleConfig = { teamInfoText: 'Group information', teamPowerText: 'Group management', dismissTeamText: 'Disband group', - transferOwnerText: 'Transfer group ownership', + transferOwnerText: 'Transfer group', newGroupOwnerText: 'Become the new group owner', beAddTeamManagersText: 'Appointed as manager', beRemoveTeamManagersText: 'Removed from manager', @@ -269,6 +272,7 @@ const LocaleConfig = { aiBlockFailedText: 'Blocking AI accounts is not allowed', aiRateLimit: 'Rate limit exceeded', aiParameterError: 'Parameter error', + teamDoNotDisturbText: 'Group Do Not Disturb', reeditText: 'Edit again', addChatMemberText: 'Add chat member', chatHistoryText: 'Chat history', @@ -300,6 +304,75 @@ const LocaleConfig = { aiTranslateEmptyText: 'Please enter the content to translate', aiSendingText: 'Large model request in response', searchTipText: 'Press Enter to search', + + // emoji 不能随便填,要用固定 key + Laugh: '[Laugh]', + Happy: '[Happy]', + Sexy: '[Sexy]', + Cool: '[Cool]', + Mischievous: '[Mischievous]', + Kiss: '[Kiss]', + Spit: '[Spit]', + Squint: '[Squint]', + Cute: '[Cute]', + Grimace: '[Grimace]', + Snicker: '[Snicker]', + Joy: '[Joy]', + Ecstasy: '[Ecstasy]', + Surprise: '[Surprise]', + Tears: '[Tears]', + Sweat: '[Sweat]', + Angle: '[Angle]', + Funny: '[Funny]', + Awkward: '[Awkward]', + Thrill: '[Thrill]', + Cry: '[Cry]', + Fretting: '[Fretting]', + Terrorist: '[Terrorist]', + Halo: '[Halo]', + Shame: '[Shame]', + Sleep: '[Sleep]', + Tired: '[Tired]', + Mask: '[Mask]', + ok: '[ok]', + AllRight: '[All right]', + Despise: '[Despise]', + Uncomfortable: '[Uncomfortable]', + Disdain: '[Disdain]', + ill: '[ill]', + Mad: '[Mad]', + Ghost: '[Ghost]', + Angry: '[Angry]', + Unhappy: '[Unhappy]', + Frown: '[Frown]', + Broken: '[Broken]', + Beckoning: '[Beckoning]', + Ok: '[Ok]', + Low: '[Low]', + Nice: '[Nice]', + Applause: '[Applause]', + GoodJob: '[Good job]', + Hit: '[Hit]', + Please: '[Please]', + Bye: '[Bye]', + First: '[First]', + Fist: '[Fist]', + GiveMeFive: '[Give me five]', + Knife: '[Knife]', + Hi: '[Hi]', + No: '[No]', + Hold: '[Hold]', + Think: '[Think]', + Pig: '[Pig]', + NoListen: '[No listen]', + NoLook: '[No look]', + NoWords: '[No words]', + Monkey: '[Monkey]', + Bomb: '[Bomb]', + Cloud: '[Cloud]', + Rocket: '[Rocket]', + Ambulance: '[Ambulance]', + Poop: '[Poop]', } export default LocaleConfig diff --git a/react/src/components/IMApp/locales/zh.ts b/react/src/components/IMApp/locales/zh.ts index 70b63f8..d2da64c 100644 --- a/react/src/components/IMApp/locales/zh.ts +++ b/react/src/components/IMApp/locales/zh.ts @@ -59,7 +59,7 @@ const LocaleConfig = { woman: '女', unknow: '未知', welcomeText: '欢迎使用云信', - notSupportMessageText: '暂不支持该消息', + notSupportMessageText: '未知消息体', applyTeamText: '申请入群', applyTeamSuccessText: '申请入群成功', rejectText: '拒绝', @@ -186,6 +186,7 @@ const LocaleConfig = { collectionSuccess: '已收藏', collectionFailed: '收藏失败', getCollectionFailed: '查询收藏列表失败', + msgRecallTimeErrorText: '已超过时间无法撤回', removeCollectionSuccess: '删除收藏成功', removeCollectionFailed: '删除收藏失败', confirmRemoveCollection: '确认删除收藏?', @@ -241,6 +242,7 @@ const LocaleConfig = { beRemoveTeamManagersText: '被移除管理员', transferTeamFailedText: '转让群主失败', transferToText: '转让给', + teamDoNotDisturbText: '群免打扰', transferTeamSuccessText: '转让群主成功', transferOwnerConfirmText: '是否确认转让群主', dismissTeamConfirmText: '是否确认解散该群组', @@ -299,6 +301,77 @@ const LocaleConfig = { aiTranslateEmptyText: '请输入需要翻译的内容', aiSendingText: '大模型请求响应中', searchTipText: 'Enter 搜索', + + // emoji 不能随便填,要用固定 key,,参考 demo + Laugh: '[大笑]', + Happy: '[开心]', + Sexy: '[色]', + Cool: '[酷]', + Mischievous: '[奸笑]', + Kiss: '[亲]', + Spit: '[伸舌头]', + Squint: '[眯眼]', + Cute: '[可爱]', + Grimace: '[鬼脸]', + Snicker: '[偷笑]', + Joy: '[喜悦]', + Ecstasy: '[狂喜]', + Surprise: '[惊讶]', + Tears: '[流泪]', + Sweat: '[流汗]', + Angle: '[天使]', + Funny: '[笑哭]', + Awkward: '[尴尬]', + Thrill: '[惊恐]', + Cry: '[大哭]', + Fretting: '[烦躁]', + Terrorist: '[恐怖]', + Halo: '[两眼冒星]', + Shame: '[害羞]', + Sleep: '[睡着]', + Sleeping: '[睡觉]', + Tired: '[冒星]', + Mask: '[口罩]', + ok: '[OK]', + AllRight: '[好吧]', + Despise: '[鄙视]', + Uncomfortable: '[难受]', + Disdain: '[不屑]', + ill: '[不舒服]', + Mad: '[愤怒]', + Ghost: '[鬼怪]', + huff: '[发怒]', + Angry: '[生气]', + Unhappy: '[不高兴]', + Frown: '[皱眉]', + Broken: '[心碎]', + Beckoning: '[心动]', + Ok: '[好的]', + Low: '[低级]', + Nice: '[赞]', + Applause: '[鼓掌]', + GoodJob: '[给力]', + Hit: '[打你]', + Please: '[阿弥陀佛]', + Bye: '[拜拜]', + First: '[第一]', + Fist: '[拳头]', + GiveMeFive: '[手掌]', + Knife: '[剪刀]', + Hi: '[招手]', + No: '[不要]', + Hold: '[举着]', + Think: '[思考]', + Pig: '[猪头]', + NoListen: '[不听]', + NoLook: '[不看]', + NoWords: '[不说]', + Monkey: '[猴子]', + Bomb: '[炸弹]', + Cloud: '[筋斗云]', + Rocket: '[火箭]', + Ambulance: '[救护车]', + Poop: '[便便]', } export default LocaleConfig diff --git a/react/src/components/IMApp/util/index.ts b/react/src/components/IMApp/util/index.ts index 36caf8e..4879fec 100644 --- a/react/src/components/IMApp/util/index.ts +++ b/react/src/components/IMApp/util/index.ts @@ -1,6 +1,6 @@ //demo 国际化 -import moment from 'moment' import { demo_en, demo_zh } from '../locales/demo_locale' + const demoLocaleMap = { zh: demo_zh, en: demo_en, @@ -25,21 +25,25 @@ export const convertSecondsToTime = (seconds: number): string => { let timeString = '' const includeHours = seconds >= 3600 + if (includeHours) { if (hours < 10) { timeString += '0' } + timeString += hours.toString() + ':' } if (minutes < 10) { timeString += '0' } + timeString += minutes.toString() + ':' if (remainingSeconds < 10) { timeString += '0' } + timeString += remainingSeconds.toString() return timeString @@ -58,27 +62,3 @@ export const callTypeMap = { audio: '1', vedio: '2', } - -export const renderMsgDate = (time) => { - const date = moment(time) - const isCurrentDay = date.isSame(moment(), 'day') - const isCurrentYear = date.isSame(moment(), 'year') - return isCurrentDay - ? date.format('HH:mm:ss') - : isCurrentYear - ? date.format('MM-DD HH:mm:ss') - : date.format('YYYY-MM-DD HH:mm:ss') -} -/** - * 解析 sessionId,形如 scene-accid - */ -export const parseSessionId = ( - sessionId: string -): { scene: string; to: string } => { - const [scene, ...to] = sessionId.split('-') - return { - scene, - // 这样处理是为了防止有些用户 accid 中自带 - - to: to.join('-'), - } -} \ No newline at end of file diff --git a/vue/package.json b/vue/package.json index 3e4c6ad..3cfb6fc 100644 --- a/vue/package.json +++ b/vue/package.json @@ -11,7 +11,7 @@ "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore" }, "dependencies": { - "@xkit-yx/im-kit-ui": "^10.x", + "@xkit-yx/im-kit-ui": "^10.5.1", "react": "^16.8.0", "react-dom": "^16.8.0", "vue": "^3.2.45" diff --git a/vue/src/App.vue b/vue/src/App.vue index 91d64d2..8ba9144 100644 --- a/vue/src/App.vue +++ b/vue/src/App.vue @@ -2,12 +2,12 @@ From 05899e9502a58569c7b08ef35a420b056f634227 Mon Sep 17 00:00:00 2001 From: Angus2333 Date: Wed, 27 Nov 2024 17:11:54 +0800 Subject: [PATCH 2/2] v10.5.0 --- .../YXUIKit/im-kit-ui/src/chat/Container.tsx | 34 +++- .../chat/components/ChatAISearch/index.tsx | 4 +- .../chat/components/ChatAITranslate/index.tsx | 2 +- .../chat/components/ChatAddMembers/index.tsx | 22 ++- .../ChatCollectionList/CollectionItem.tsx | 10 +- .../components/ChatCollectionList/index.tsx | 4 +- .../chat/components/ChatCreateTeam/index.tsx | 134 -------------- .../ChatCreateTeam/style/index.less | 3 - .../components/ChatCreateTeam/style/index.ts | 4 - .../ChatMentionMemberList.tsx | 93 ++++++---- .../components/ChatMessageInput/index.tsx | 127 +++++++------ .../style/chatAtMemberList.less | 3 +- .../ChatMessageInput/style/index.less | 1 - .../chat/components/ChatMessageItem/index.tsx | 50 ++++-- .../components/ChatP2pMessageList/index.tsx | 8 +- .../ChatP2pMessageList/style/index.less | 2 +- .../components/ChatTeamMessageList/index.tsx | 3 +- .../ChatTeamMessageList/style/index.less | 2 +- .../ChatTeamSetting/GroupActionModal.tsx | 167 ------------------ .../ChatTeamSetting/GroupDetail.tsx | 8 +- .../components/ChatTeamSetting/GroupItem.tsx | 4 +- .../components/ChatTeamSetting/GroupList.tsx | 2 +- .../components/ChatTeamSetting/GroupPower.tsx | 13 +- .../chat/components/ChatTeamSetting/index.tsx | 29 ++- .../style/groupActionModal.less | 69 -------- .../ChatTeamSetting/style/groupItem.less | 2 + .../ChatTeamSetting/style/groupList.less | 2 +- .../ChatTeamSetting/style/index.less | 8 + .../src/chat/components/ChatTopMsg/index.tsx | 6 +- .../YXUIKit/im-kit-ui/src/chat/constant.ts | 9 - .../src/chat/containers/p2pChatContainer.tsx | 55 +++++- .../src/chat/containers/teamChatContainer.tsx | 119 +++++++++++-- .../common/components/CommonIcon/index.tsx | 4 +- .../components/CommonParseSession/index.tsx | 48 +++-- .../CommonParseSession/style/index.less | 6 +- .../components/ComplexAvatar/Container.tsx | 2 +- .../components/CreateTeamModal/index.tsx | 4 +- .../CreateTeamModal/style/index.less | 4 +- .../common/components/CrudeAvatar/index.tsx | 104 ++++++----- .../components/FriendSelect/Container.tsx | 18 -- .../FriendSelect/FriendSelectUI.tsx | 121 ------------- .../common/components/FriendSelect/index.tsx | 63 ++++--- .../components/FriendSelect/style/index.less | 2 +- .../GroupAvatarSelect/style/index.less | 2 +- .../common/components/MyAvatar/Container.tsx | 2 +- .../components/MyUserCard/style/index.less | 2 +- .../common/components/ReadPercent/index.tsx | 132 +++++++++----- .../components/ReadPercent/style/index.less | 52 +++++- .../YXUIKit/im-kit-ui/src/common/constant.ts | 90 ---------- .../src/common/contextManager/Provider.tsx | 139 +++++++-------- .../im-kit-ui/src/common/locales/zh.ts | 6 +- .../src/contact/ai-list/Container.tsx | 2 +- .../src/contact/ai-list/components/AIItem.tsx | 2 +- .../src/contact/ai-list/components/AIList.tsx | 2 +- .../src/contact/contact-info/Container.tsx | 4 +- .../contact-list/style/contactItem.less | 1 + .../contact-list/style/contactList.less | 2 +- .../src/contact/group-list/Container.tsx | 4 +- .../group-list/components/GroupItem.tsx | 2 +- .../group-list/components/GroupList.tsx | 2 +- .../src/contact/msg-list/Container.tsx | 1 + .../contact/msg-list/components/MsgItem.tsx | 2 +- .../im-kit-ui/src/conversation/Container.tsx | 75 +++++++- .../components/ConversationItem.tsx | 4 +- .../components/ConversationList.tsx | 93 ++++++++-- .../src/conversation/components/GroupItem.tsx | 29 +-- .../src/conversation/components/P2PItem.tsx | 9 +- .../src/conversation/components/pinAIItem.tsx | 4 +- .../conversation/style/conversationItem.less | 91 ---------- .../conversation/style/conversationList.less | 17 -- react/src/YXUIKit/im-kit-ui/src/index.ts | 6 +- .../im-kit-ui/src/search/add/Container.tsx | 1 + .../add/components/AddFriendModal/index.tsx | 4 +- .../add/components/CreateModal/index.tsx | 154 ---------------- .../add/components/JoinTeamModal/index.tsx | 4 +- .../src/search/add/style/addModal.less | 2 +- .../src/search/add/style/createModal.less | 72 -------- .../im-kit-ui/src/search/search/Container.tsx | 11 +- .../search/components/SearchModal/index.tsx | 6 +- react/src/YXUIKit/im-kit-ui/src/utils.ts | 48 ++++- 80 files changed, 1061 insertions(+), 1393 deletions(-) delete mode 100644 react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/index.tsx delete mode 100644 react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/style/index.less delete mode 100644 react/src/YXUIKit/im-kit-ui/src/chat/components/ChatCreateTeam/style/index.ts delete mode 100644 react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupActionModal.tsx delete mode 100644 react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/style/groupActionModal.less delete mode 100644 react/src/YXUIKit/im-kit-ui/src/chat/constant.ts delete mode 100644 react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/Container.tsx delete mode 100644 react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx delete mode 100644 react/src/YXUIKit/im-kit-ui/src/common/constant.ts delete mode 100644 react/src/YXUIKit/im-kit-ui/src/conversation/style/conversationItem.less delete mode 100644 react/src/YXUIKit/im-kit-ui/src/conversation/style/conversationList.less delete mode 100644 react/src/YXUIKit/im-kit-ui/src/search/add/components/CreateModal/index.tsx delete mode 100644 react/src/YXUIKit/im-kit-ui/src/search/add/style/createModal.less 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] +}