diff --git a/react/src/YXUIKit/im-kit-ui/package.json b/react/src/YXUIKit/im-kit-ui/package.json index 04a8dc9..f129d72 100644 --- a/react/src/YXUIKit/im-kit-ui/package.json +++ b/react/src/YXUIKit/im-kit-ui/package.json @@ -1,6 +1,6 @@ { "name": "@xkit-yx/im-kit-ui", - "version": "9.8.0", + "version": "9.8.6", "description": "云信即时通讯组件", "license": "MIT", "main": "lib/index.js", @@ -62,7 +62,7 @@ "antd": "^4.16.3", "mobx": "^6.6.1", "mobx-react": "^7.5.2", - "react-string-replace": "^1.1.0" - }, - "gitHead": "2460a56b946207f24db9a8d74c10d3776aa5f267" + "react-string-replace": "^1.1.0", + "react-virtualized": "^9.22.5" + } } diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatForwardModal/index.tsx index 2152d1b..32877c2 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 @@ -1,4 +1,4 @@ -import React, { useEffect, useMemo, useState } from 'react' +import React, { useCallback, useEffect, useMemo, useState } from 'react' import { Input, message } from 'antd' import { useTranslation, @@ -91,31 +91,34 @@ const ChatForwardModal: React.FC = ({ store.uiStore, ]) - const itemAvatarRender = (data: SelectModalItemProps) => { - const { scene, to } = parseSessionId(data.key) - if (scene === 'p2p') { - return ( - - ) - } - if (scene === 'team') { - const team = store.teamStore.teams.get(to) - return ( - - ) - } - return null - } + const itemAvatarRender = useCallback( + (data: SelectModalItemProps) => { + const { scene, to } = parseSessionId(data.key) + if (scene === 'p2p') { + return ( + + ) + } + if (scene === 'team') { + const team = store.teamStore.teams.get(to) + return ( + + ) + } + return null + }, + [commonPrefix, store.teamStore.teams] + ) const handleCommentChange = (e: any) => { setComment(e.target.value) @@ -146,15 +149,17 @@ const ChatForwardModal: React.FC = ({ } } + const handleSearchChang = useCallback((value) => { + setIsSearching(!!value) + }, []) + return ( { - setIsSearching(!!value) - }} + onSearchChange={handleSearchChang} type="radio" min={1} okText={t('sendBtnText')} diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatGroupTransferModal/index.tsx index 485ca57..cc9fe45 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 @@ -1,4 +1,4 @@ -import React, { useMemo, useState } from 'react' +import React, { useCallback, useMemo, useState } from 'react' import { message } from 'antd' import { ComplexAvatarContainer, @@ -58,9 +58,9 @@ const GroupTransferModal: React.FC = ({ } } - const handleSelect = (value: SelectModalItemProps[]) => { + const handleSelect = useCallback((value: SelectModalItemProps[]) => { setSelectedMemberId(value[0].key) - } + }, []) const datasource: SelectModalItemProps[] = useMemo(() => { const _showMembers = members.map((item) => { @@ -77,16 +77,19 @@ const GroupTransferModal: React.FC = ({ return _showMembers }, [[members]]) - const itemAvatarRender = (data: SelectModalItemProps) => { - return ( - - ) - } + const itemAvatarRender = useCallback( + (data: SelectModalItemProps) => { + return ( + + ) + }, + [commonPrefix] + ) return ( = observer( .getTeamMember(teamId) .filter((item) => item.account !== store.userStore.myUserInfo.account) - const datasource = teamMembers.map((item) => ({ - key: item.account, - label: store.uiStore.getAppellation({ - account: item.account, - teamId: item.teamId, - }), - })) + const datasource = useMemo( + () => + teamMembers.map((item) => ({ + key: item.account, + label: store.uiStore.getAppellation({ + account: item.account, + teamId: item.teamId, + }), + })), + [store.uiStore, teamMembers] + ) const teamManagerAccounts = teamMembers .filter((item) => item.type === 'manager') .map((item) => item.account) - const itemAvatarRender = (data: SelectModalItemProps) => { - return ( - - ) - } + const itemAvatarRender = useCallback( + (data: SelectModalItemProps) => { + return ( + + ) + }, + [commonPrefix] + ) const handleOk = async (data: SelectModalItemProps[]) => { try { diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupList.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/GroupList.tsx index e385c51..fbda65c 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,9 +1,10 @@ -import React, { FC, useMemo, useState } from 'react' +import React, { FC, useCallback, useMemo, useState } from 'react' import { GroupItem, GroupItemProps } from './GroupItem' import { TeamMember } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' import { Input } from 'antd' import { SearchOutlined } from '@ant-design/icons' import { useStateContext, useTranslation } from '../../../common' +import { AutoSizer, List } from 'react-virtualized' export interface GroupListProps { myMemberInfo: TeamMember @@ -46,6 +47,44 @@ const GroupList: FC = ({ return _sortedMembers }, [members, groupSearchText]) + const rowRenderer = useCallback( + ({ index, key, style }) => { + const item = showMembers[index] + const itemProps: GroupItemProps & { + renderKey: string + renderIndex: number + renderStyle: React.CSSProperties + } = { + member: item, + onRemoveTeamMemberClick, + afterSendMsgClick, + myMemberInfo, + prefix, + commonPrefix, + renderIndex: index, + renderKey: key, + renderStyle: style, + } + + return ( + renderTeamMemberItem?.(itemProps) ?? ( +
+ +
+ ) + ) + }, + [ + afterSendMsgClick, + commonPrefix, + myMemberInfo, + onRemoveTeamMemberClick, + prefix, + renderTeamMemberItem, + showMembers, + ] + ) + return (
= ({ placeholder={t('searchTeamMemberPlaceholder')} onChange={(e) => handleSearch(e.target.value)} /> - {showMembers.length ? ( - showMembers.map((item) => { - const itemProps = { - member: item, - onRemoveTeamMemberClick, - afterSendMsgClick, - myMemberInfo, - prefix, - commonPrefix, - } - return ( - renderTeamMemberItem?.(itemProps) ?? ( - - ) - ) - }) - ) : ( -
{t('searchNoResText')}
- )} +
+ {showMembers.length ? ( + + {({ height, width }) => ( + + )} + + ) : ( +
{t('searchNoResText')}
+ )} +
) } diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/style/groupList.less b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/style/groupList.less index d1c43a6..62e169f 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/style/groupList.less +++ b/react/src/YXUIKit/im-kit-ui/src/chat/components/ChatTeamSetting/style/groupList.less @@ -6,9 +6,14 @@ .@{prefix-cls}-wrap { width: 100%; height: 100%; - overflow-y: auto; padding: 0 10px; } +.@{prefix-cls}-list { + width: 100%; + height: calc(100% - 57px); + overflow-y: auto; +} + .@{prefix-cls}-input { margin: 15px 0 10px 0; background-color: @yx-background-color-6!important; diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/containers/p2pChatContainer.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/containers/p2pChatContainer.tsx index 3550a15..29c02b7 100644 --- a/react/src/YXUIKit/im-kit-ui/src/chat/containers/p2pChatContainer.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/chat/containers/p2pChatContainer.tsx @@ -375,6 +375,21 @@ const P2pChatContainer: React.FC = observer( [msgOperMenu, store.msgStore] ) + const resetSettingState = useCallback(() => { + setAction(undefined) + setGroupCreateVisible(false) + setSettingDrawerVisible(false) + }, []) + + const resetState = useCallback(() => { + resetSettingState() + setInputValue('') + setLoadingMore(false) + setNoMore(false) + setReceiveMsgBtnVisible(false) + setForwardMessage(undefined) + }, [resetSettingState]) + const onGroupCreate = useCallback( async ({ name, @@ -405,24 +420,9 @@ const P2pChatContainer: React.FC = observer( } } }, - [store.teamStore, t] + [store.teamStore, t, resetSettingState] ) - const resetSettingState = () => { - setAction(undefined) - setGroupCreateVisible(false) - setSettingDrawerVisible(false) - } - - const resetState = useCallback(() => { - resetSettingState() - setInputValue('') - setLoadingMore(false) - setNoMore(false) - setReceiveMsgBtnVisible(false) - setForwardMessage(undefined) - }, []) - const handleForwardModalSend = () => { scrollToBottom() setForwardMessage(undefined) @@ -593,7 +593,7 @@ const P2pChatContainer: React.FC = observer( return () => { nim.off('msg', onMsg) } - }, [nim, sessionId]) + }, [nim, sessionId, scrollToBottom]) return session ? (
diff --git a/react/src/YXUIKit/im-kit-ui/src/chat/containers/teamChatContainer.tsx b/react/src/YXUIKit/im-kit-ui/src/chat/containers/teamChatContainer.tsx index bacb10e..4244533 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 @@ -620,6 +620,22 @@ const TeamChatContainer: React.FC = observer( afterTransferTeam?.(team.teamId) }, [afterTransferTeam, team.teamId]) + const resetSettingState = useCallback(() => { + setNavHistoryStack([]) + setAction(undefined) + setGroupAddMembersVisible(false) + setSettingDrawerVisible(false) + }, []) + + const resetState = useCallback(() => { + resetSettingState() + setInputValue('') + setLoadingMore(false) + setNoMore(false) + setReceiveMsgBtnVisible(false) + setForwardMessage(undefined) + }, [resetSettingState]) + const onAddMembersClick = useCallback(() => { if (team.inviteMode === 'manager' && !isGroupOwner && !isGroupManager) { message.error(t('noPermission')) @@ -649,7 +665,7 @@ const TeamChatContainer: React.FC = observer( } } }, - [store.teamMemberStore, team.teamId, t] + [store.teamMemberStore, team.teamId, t, resetSettingState] ) const onRemoveTeamMember = useCallback( @@ -759,22 +775,6 @@ const TeamChatContainer: React.FC = observer( [store.teamStore, team.teamId, t] ) - const resetSettingState = () => { - setNavHistoryStack([]) - setAction(undefined) - setGroupAddMembersVisible(false) - setSettingDrawerVisible(false) - } - - const resetState = useCallback(() => { - resetSettingState() - setInputValue('') - setLoadingMore(false) - setNoMore(false) - setReceiveMsgBtnVisible(false) - setForwardMessage(undefined) - }, []) - const handleForwardModalSend = () => { scrollToBottom() setForwardMessage(undefined) @@ -960,7 +960,7 @@ const TeamChatContainer: React.FC = observer( return () => { nim.off('msg', onMsg) } - }, [nim, sessionId]) + }, [nim, sessionId, scrollToBottom]) useEffect(() => { // const onDismissTeam = (data: { teamId: string }) => { diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx index 8b20729..b9d90b3 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/FriendSelect/FriendSelectUI.tsx @@ -1,9 +1,10 @@ -import React, { FC, useMemo } from 'react' +import React, { FC, useCallback, useMemo } from 'react' import { Divider, message, Spin } from 'antd' import { FriendSelectItem } from './FriendSelectItem' import { NimKitCoreTypes } from '@xkit-yx/core-kit' import { groupByPy } from '../../../utils' import { useTranslation } from '../../hooks/useTranslation' +import { AutoSizer, List } from 'react-virtualized' export interface FriendSelectUIProps { list: NimKitCoreTypes.IFriendInfo[] @@ -37,24 +38,29 @@ export const FriendSelectUI: FC = ({ }, false ) + .map((item) => item.data) + .flat() }, [list]) - 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 + const handleSelect = useCallback( + (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) } - _selectedAccounts = selectedAccounts.concat(account) - } else if (!selected && selectedAccounts.includes(account)) { - _selectedAccounts = selectedAccounts.filter((item) => item !== account) - } - const _selectedList = list.filter((item) => - _selectedAccounts.includes(item.account) - ) - onSelect(_selectedList) - } + const _selectedList = list.filter((item) => + _selectedAccounts.includes(item.account) + ) + onSelect(_selectedList) + }, + [list, max, onSelect, selectedAccounts, t] + ) const selectedList = useMemo(() => { return list.filter((item) => selectedAccounts.includes(item.account)) @@ -66,6 +72,26 @@ export const FriendSelectUI: FC = ({ ) }, [list, selectedAccounts]) + const rowRenderer = useCallback( + ({ index, key, style }) => { + const item = dataSource[index] + + return ( +
+ +
+ ) + }, + [dataSource, handleSelect, prefix, selectedAccounts] + ) + return (
{loading ? ( @@ -73,23 +99,18 @@ export const FriendSelectUI: FC = ({ ) : ( <>
- {dataSource.map(({ key, data }) => { - return ( -
-
{key}
- {data.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 9f6bf5c..b3a8159 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 @@ -50,7 +50,7 @@ &-item { display: flex; align-items: center; - margin-top: 8px; + padding: 4px 0; &-checkbox.@{ant-prefix}-checkbox-wrapper { margin-right: 8px; diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx index 29cb944..1b61b8a 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/index.tsx @@ -1,15 +1,19 @@ -import React, { useEffect, useMemo, useState } from 'react' -import { Modal, Input, Radio, Button, Checkbox } from 'antd' +import React, { useCallback, useEffect, useMemo, useState } from 'react' +import { Modal, Input, Radio, Button, Checkbox, RadioChangeEvent } from 'antd' import { CloseOutlined, SearchOutlined } from '@ant-design/icons' import { useTranslation } from '../../../common' +import { AutoSizer, List } from 'react-virtualized' +import { CheckboxChangeEvent } from 'antd/lib/checkbox' export interface SelectModalItemProps { key: string label: string disabled?: boolean + hide?: boolean } export interface SelectModalProps { + tabRenderer?: React.ReactNode datasource: SelectModalItemProps[] visible: boolean onSearchChange?: (value: string) => void @@ -22,11 +26,13 @@ export interface SelectModalProps { defaultValue?: string[] bottomRenderer?: React.ReactNode itemAvatarRender?: (data: SelectModalItemProps) => React.ReactNode + recentRenderer?: React.ReactNode type?: 'radio' | 'checkbox' max?: number min?: number searchPlaceholder?: string leftTitle?: string + showLeftTitle?: boolean rightTitle?: string closable?: boolean width?: number @@ -37,6 +43,7 @@ export interface SelectModalProps { const emptyArr = [] export const SelectModal: React.FC = ({ + tabRenderer, datasource, visible, onSearchChange, @@ -49,11 +56,13 @@ export const SelectModal: React.FC = ({ defaultValue = emptyArr, bottomRenderer, itemAvatarRender, + recentRenderer, type = 'radio', max = Infinity, min = 0, searchPlaceholder, leftTitle, + showLeftTitle = true, rightTitle, closable = true, width = 720, @@ -85,33 +94,59 @@ export const SelectModal: React.FC = ({ } }, [visible]) - // const finalDatasource = useMemo(() => { - // return datasource.filter((item) => item.label.includes(searchText)) - // }, [searchText, datasource]) + const visibleDatasource = useMemo(() => { + return datasource.filter( + (item) => !item.hide && item.label.includes(searchText) + ) + }, [searchText, datasource]) const _prefix = `${prefix}-select-modal` - const getItemsFromKeys = (keys: string[]) => { - return datasource.filter((item) => keys.some((j) => j === item.key)) - } + const getItemsFromKeys = useCallback( + (keys: string[]) => { + return datasource + .filter((item) => keys.some((j) => j === item.key)) + .reduce((unique, item) => { + if (!unique.some((uniqueItem) => uniqueItem.key === item.key)) { + unique.push(item) + } + + return unique + }, [] as SelectModalItemProps[]) + }, + [datasource] + ) const handleSearchTextChange = (e: any) => { const value = e.target.value + setSearchText(value) onSearchChange?.(value) } - const handleSelect = (e: any) => { - let value: string[] = [] - if (type === 'radio') { - value = [e.target.value] - } else { - value = e - } - setSelected(value) - onSelectChange?.(getItemsFromKeys(value)) + const handleRadioSelect = (e: RadioChangeEvent) => { + setSelected([e.target.value]) + onSelectChange?.(getItemsFromKeys([e.target.value])) } + const handleCheckboxSelect = useCallback( + (e: CheckboxChangeEvent) => { + const { value, checked } = e.target + + let newSelected = selected + + if (checked && !selected.includes(value)) { + newSelected = [...selected, value] + } else { + newSelected = selected.filter((i) => i !== value) + } + + setSelected(newSelected) + onSelectChange?.(getItemsFromKeys(newSelected)) + }, + [getItemsFromKeys, onSelectChange, selected] + ) + const handleSelectDelete = (item: SelectModalItemProps) => { setSelected(selected.filter((i) => i !== item.key)) onDelete?.(item) @@ -134,58 +169,101 @@ export const SelectModal: React.FC = ({ } } + const radioRowRenderer = useCallback( + ({ index, key, style }) => { + const item = visibleDatasource[index] + + return ( +
+
+ + {itemAvatarRender?.(item)} + {item.label} +
+
+ ) + }, + [_prefix, itemAvatarRender, selected, visibleDatasource] + ) + + const checkboxRowRenderer = useCallback( + ({ index, key, style }) => { + const item = visibleDatasource[index] + + return ( +
+
+ = max} + checked={selected.includes(item.key)} + onChange={handleCheckboxSelect} + /> + {itemAvatarRender?.(item)} + {item.label} +
+
+ ) + }, + [ + _prefix, + handleCheckboxSelect, + itemAvatarRender, + max, + selected, + visibleDatasource, + ] + ) + const renderRadio = () => { return ( - {datasource.map((item) => { - const isVisible = item.label.includes(searchText) - return ( -
- - {itemAvatarRender?.(item)} - {item.label} -
- ) - })} + + {({ height, width }) => ( + + )} +
) } const renderCheckbox = () => { return ( - = max} - > - {datasource.map((item) => { - const isVisible = item.label.includes(searchText) - return ( -
- - {itemAvatarRender?.(item)} - {item.label} -
- ) - })} -
+
+ + {({ height, width }) => ( + + )} + +
) } @@ -193,9 +271,11 @@ export const SelectModal: React.FC = ({ return selected.length ? selected.map((key) => { const item = datasource.find((item) => item.key === key) + if (!item) { return null } + return (
{itemAvatarRender?.(item)} @@ -235,12 +315,17 @@ export const SelectModal: React.FC = ({ onChange={handleSearchTextChange} placeholder={searchPlaceholder} /> -
- {searchText ? t('searchText') : leftTitle} -
+ {!searchText ? recentRenderer : null} + {showLeftTitle ? ( +
+ {searchText ? t('searchText') : leftTitle} +
+ ) : null} + {tabRenderer}
- {!datasource.filter((item) => item.label.includes(searchText)) - .length ? ( + {!datasource.filter( + (item) => !item.hide && item.label.includes(searchText) + ).length ? (
{t('searchNoResText')}
diff --git a/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/style/index.less b/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/style/index.less index 9c1a0d9..ef9ac03 100644 --- a/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/common/components/SelectModal/style/index.less @@ -7,6 +7,8 @@ border-radius: @yx-primary-border-radius; display: flex; flex-direction: row; + min-height: 400px; + max-height: 540px; .@{ant-prefix}-checkbox-wrapper { margin-right: 8px; @@ -14,15 +16,17 @@ &-left { flex: 1; + display: flex; + flex-direction: column; padding: 12px; border-right: 1px solid @yx-border-color-10; } &-right { flex: 1; + display: flex; + flex-direction: column; padding: 8px; - max-height: 388px; - overflow-y: auto; } &-l-title { @@ -39,12 +43,12 @@ } &-l-list { - height: 300px; + flex: 1; overflow-y: auto; } &-r-list { - max-height: 350px; + flex: 1; overflow-y: auto; } @@ -60,6 +64,7 @@ } &-item { + display: flex; padding: 8px 12px; width: 100%; align-items: center; diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendList.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendList.tsx index 82c8dd8..6c95eff 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendList.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/contact/friend-list/components/FriendList.tsx @@ -1,9 +1,9 @@ -import React, { FC, useMemo } from 'react' +import React, { FC, useCallback, useMemo } from 'react' import { Utils, useTranslation } from '../../../common' import { NimKitCoreTypes } from '@xkit-yx/core-kit' import { FriendItem } from './FriendItem' import { Spin, Empty } from 'antd' -// import { List } from 'react-virtualized' +import { AutoSizer, List } from 'react-virtualized' export interface FriendListProps { list: NimKitCoreTypes.IFriendInfo[] @@ -40,44 +40,29 @@ export const FriendList: FC = ({ }, false ) - const res: (NimKitCoreTypes.IFriendInfo | string)[] = [] - group.forEach((item) => { - if (!res.includes(item.key)) { - res.push(item.key) - } - res.push(...item.data) - }) - return res + + return group.map((item) => item.data).flat() }, [list]) - // const rowHeight = (index: number) => { - // if (typeof dataSource[index] === 'string') { - // return 57 - // } - // return 46 - // } + const rowRenderer = useCallback( + ({ index, key, style }) => { + const item = dataSource[index] - // const rowRenderer = (data: any) => { - // const item = dataSource[data.index] - // // console.log('rowRenderer:', key, index, item) - // if (typeof item === 'string') { - // return ( - //
- // {item} - //
- // ) - // } - // return ( - // - // ) - // } + return ( +
+ +
+ ) + }, + [afterSendMsgClick, commonPrefix, dataSource, onItemClick, prefix] + ) return (
@@ -96,33 +81,18 @@ export const FriendList: FC = ({ ) ) : ( - // - dataSource.map((item) => { - if (typeof item === 'string') { - return ( -
- {item} -
- ) - } - return ( - + {({ height, width }) => ( + - ) - }) + )} + )}
diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupList.tsx b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/components/GroupList.tsx index 2f50cb1..c68db17 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,8 +1,9 @@ -import React, { FC } from 'react' +import React, { FC, useCallback } from 'react' import { GroupItem } from './GroupItem' import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' import { useTranslation } from '../../../common' import { Spin, Empty } from 'antd' +import { AutoSizer, List } from 'react-virtualized' export interface GroupListProps { list: Team[] @@ -25,6 +26,24 @@ export const GroupList: FC = ({ const { t } = useTranslation() + const rowRenderer = useCallback( + ({ index, key, style }) => { + const item = list[index] + + return ( +
+ +
+ ) + }, + [list, onItemClick, prefix] + ) + return (
@@ -40,14 +59,18 @@ export const GroupList: FC = ({ ) ) : ( - list.map((item) => ( - - )) + + {({ height, width }) => ( + + )} + )}
diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/style/groupItem.less b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/style/groupItem.less index f857dc1..87507de 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/style/groupItem.less +++ b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/style/groupItem.less @@ -10,10 +10,7 @@ transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1); width: 100%; border-radius: @yx-primary-border-radius; - - &:not(:last-child) { - border-bottom: 1px solid @yx-border-color-7; - } + border-bottom: 1px solid @yx-border-color-7; &:hover { background-color: @yx-background-color-4; diff --git a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/style/groupList.less b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/style/groupList.less index 2ccb058..d658d1c 100644 --- a/react/src/YXUIKit/im-kit-ui/src/contact/group-list/style/groupList.less +++ b/react/src/YXUIKit/im-kit-ui/src/contact/group-list/style/groupList.less @@ -21,4 +21,5 @@ .@{group-content-prefix-cls} { padding: 0 16px; + height: calc(100% - 71px); } diff --git a/react/src/YXUIKit/im-kit-ui/src/search/search/Container.tsx b/react/src/YXUIKit/im-kit-ui/src/search/search/Container.tsx index aac7b58..9400827 100644 --- a/react/src/YXUIKit/im-kit-ui/src/search/search/Container.tsx +++ b/react/src/YXUIKit/im-kit-ui/src/search/search/Container.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react' +import React, { useCallback, useState } from 'react' import { useTranslation, useEventTracking, useStateContext } from '../../common' import { NimKitCoreTypes } from '@xkit-yx/core-kit' import { SearchOutlined } from '@ant-design/icons' @@ -57,23 +57,26 @@ export const SearchContainer: React.FC = observer( const [visible, setVisible] = useState(false) - const handleChat = async (item: SectionListItem) => { - let scene: TMsgScene - let to = '' - if ((item as NimKitCoreTypes.IFriendInfo).account) { - scene = 'p2p' - to = (item as NimKitCoreTypes.IFriendInfo).account - } else if ((item as Team).teamId) { - scene = 'team' - to = (item as Team).teamId - } else { - throw Error('unknow scene') - } + const handleChat = useCallback( + async (item: SectionListItem) => { + let scene: TMsgScene + let to = '' + if ((item as NimKitCoreTypes.IFriendInfo).account) { + scene = 'p2p' + to = (item as NimKitCoreTypes.IFriendInfo).account + } else if ((item as Team).teamId) { + scene = 'team' + to = (item as Team).teamId + } else { + throw Error('unknow scene') + } - await store.sessionStore.insertSessionActive(scene, to) - setVisible(false) - onClickChat?.() - } + await store.sessionStore.insertSessionActive(scene, to) + setVisible(false) + onClickChat?.() + }, + [onClickChat, store.sessionStore] + ) const handleOpenModal = async () => { setVisible(true) diff --git a/react/src/YXUIKit/im-kit-ui/src/search/search/components/SearchModal/index.tsx b/react/src/YXUIKit/im-kit-ui/src/search/search/components/SearchModal/index.tsx index 0dc57df..4b59a9a 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 @@ -1,9 +1,10 @@ -import React, { useState, useMemo } from 'react' +import React, { useState, useMemo, useCallback } from 'react' import { Modal } from 'antd' import { SearchInput, CrudeAvatar, useTranslation } from '../../../../common' import { CrudeAvatarProps } from '../../../../common/components/CrudeAvatar' import { NimKitCoreTypes } from '@xkit-yx/core-kit' import { Team } from 'nim-web-sdk-ng/dist/NIM_BROWSER_SDK/TeamServiceInterface' +import { AutoSizer, List } from 'react-virtualized' export interface SearchItemProps extends CrudeAvatarProps { onClick: () => void @@ -20,7 +21,6 @@ const SearchItem: React.FC = ({ return (
- {/* TODO CrudeAvatar 可以不用了,p2p 都用 ComplexAvatar */} {props.alias || props.nick || props.account} @@ -32,8 +32,7 @@ const SearchItem: React.FC = ({ export type SectionListItem = NimKitCoreTypes.IFriendInfo | Team export type Section = { - id: string - title: string + id: 'friends' | 'groups' list: SectionListItem[] } @@ -70,65 +69,126 @@ const SearchModal: React.FC = ({ const sections: Section[] = useMemo(() => { return [ { - id: 'friends', - title: t('searchFriendTitle'), + id: 'friends' as Section['id'], list: friends, }, { - id: 'groups', - title: t('searchTeamTitle'), + id: 'groups' as Section['id'], list: teams, }, ].filter((item) => !!item.list.length) - }, [friends, teams, t]) - - const searchedSections: Section[] = useMemo(() => { - return sections - .map((item) => { - if (item.id === 'friends') { - return { - ...item, - list: item.list.filter((item) => { - return ( - (item as NimKitCoreTypes.IFriendInfo).alias || - (item as NimKitCoreTypes.IFriendInfo).nick || - (item as NimKitCoreTypes.IFriendInfo).account - ).includes(searchText) - }), + }, [friends, teams]) + + const searchedSections: (SectionListItem | 'friends' | 'groups')[] = + useMemo(() => { + const finalSections = sections + .map((item) => { + if (item.id === 'friends') { + return { + ...item, + list: item.list.filter((item) => { + return ( + (item as NimKitCoreTypes.IFriendInfo).alias || + (item as NimKitCoreTypes.IFriendInfo).nick || + (item as NimKitCoreTypes.IFriendInfo).account + ).includes(searchText) + }), + } } - } - if (item.id === 'groups') { - return { - ...item, - list: item.list.filter((item) => { - return ((item as Team).name || (item as Team).teamId).includes( - searchText - ) - }), + + if (item.id === 'groups') { + return { + ...item, + list: item.list.filter((item) => { + return ((item as Team).name || (item as Team).teamId).includes( + searchText + ) + }), + } } + + return { ...item } + }) + .filter((item) => !!item.list.length) + + const res: (SectionListItem | 'friends' | 'groups')[] = [] + + finalSections.forEach((item) => { + if (item.id === 'friends') { + res.push('friends') + item.list.forEach((item) => { + res.push(item) + }) + } else if (item.id === 'groups') { + res.push('groups') + item.list.forEach((item) => { + res.push(item) + }) } - return { ...item } }) - .filter((item) => !!item.list.length) - }, [sections, searchText]) + + return res + }, [sections, searchText]) const handleSearchChange = (value: string) => { setSearchText(value) } - const handleItemClick = (data: SectionListItem) => { - onResultItemClick(data) - resetState() - } + const resetState = useCallback(() => { + setSearchText('') + }, []) + + const handleItemClick = useCallback( + (data: SectionListItem) => { + onResultItemClick(data) + resetState() + }, + [onResultItemClick, resetState] + ) const handleCancel = () => { onCancel() resetState() } - const resetState = () => { - setSearchText('') - } + const rowRenderer = useCallback( + ({ index, key, style }) => { + const item = searchedSections[index] + + if (typeof item === 'string') { + return ( +
+
+ {t(item === 'friends' ? 'searchFriendTitle' : 'searchTeamTitle')} +
+
+ ) + } + + return ( +
+ handleItemClick(item)} + prefix={prefix} + account={ + (item as NimKitCoreTypes.IFriendInfo).account || + (item as Team).teamId + } + avatar={item.avatar} + nick={ + (item as NimKitCoreTypes.IFriendInfo).nick || (item as Team).name + } + alias={(item as NimKitCoreTypes.IFriendInfo).alias || ''} + /> +
+ ) + }, + [_prefix, prefix, searchedSections, t, handleItemClick] + ) return ( = ({ ) ) : (
- {searchedSections.map((section) => ( -
-
- {section.title} -
- {section.list.map((item) => ( - handleItemClick(item)} - prefix={prefix} - // @ts-ignore - account={item.account || item.teamId} - avatar={item.avatar} - // @ts-ignore - nick={item.nick || item.name} - // @ts-ignore - alias={item.alias || ''} - /> - ))} -
- ))} + + {({ height, width }) => ( + + )} +
)}
diff --git a/react/src/YXUIKit/im-kit-ui/src/search/search/style/index.less b/react/src/YXUIKit/im-kit-ui/src/search/search/style/index.less index b4fa336..4b65975 100644 --- a/react/src/YXUIKit/im-kit-ui/src/search/search/style/index.less +++ b/react/src/YXUIKit/im-kit-ui/src/search/search/style/index.less @@ -49,7 +49,7 @@ &-content-section-title { color: @yx-border-color-7; border-bottom: 1px solid @yx-border-color-7; - margin-bottom: 8px; + padding-bottom: 8px; } &-content-section-item {