feat: add search friend feature #157

include can search by nickname
pull/177/head
moonrailgun 1 year ago
parent de63236447
commit a7fe82eff3

@ -1,38 +1,8 @@
import { useQueries, useQuery, useQueryClient } from '@tanstack/react-query'; import { useQuery, useQueryClient } from '@tanstack/react-query';
import { import { getUserOnlineStatus } from '../model/user';
fetchUserInfo,
getUserOnlineStatus,
UserBaseInfo,
} from '../model/user';
import { isValidStr } from '../utils/string-helper';
export { useQuery, useQueryClient }; export { useQuery, useQueryClient };
/**
*
*/
export function useCachedUserInfo(
userId: string | null,
refetch = false
): UserBaseInfo | Record<string, never> {
const { data } = useQuery(
['user', userId],
() => {
if (!isValidStr(userId)) {
return {};
}
return fetchUserInfo(userId);
},
{
staleTime: 2 * 60 * 60 * 1000, // 缓存2小时
refetchOnMount: refetch ? 'always' : true,
}
);
return data ?? {};
}
/** /**
* *
*/ */

@ -0,0 +1,19 @@
import { getCachedUserInfo } from '../../cache/cache';
import type { UserBaseInfo } from '../../model/user';
import { useAsync } from '../useAsync';
/**
*
*/
export function useCachedUserInfo(
userId: string,
refetch = false
): UserBaseInfo | Record<string, never> {
const { value: userInfo = {} } = useAsync(async () => {
const users = getCachedUserInfo(userId, refetch);
return users;
}, [userId, refetch]);
return userInfo;
}

@ -1,4 +1,6 @@
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import { useFriendNicknameMap } from '../redux/hooks/useFriendNickname';
import type { UserBaseInfo } from 'tailchat-types';
export interface UseSearchOptions<T> { export interface UseSearchOptions<T> {
dataSource: T[]; dataSource: T[];
@ -21,3 +23,34 @@ export function useSearch<T>(options: UseSearchOptions<T>) {
searchResult, searchResult,
}; };
} }
/**
*
*/
export function useUserSearch(userInfos: UserBaseInfo[]) {
const friendNicknameMap = useFriendNicknameMap();
const { searchText, setSearchText, isSearching, searchResult } = useSearch({
dataSource: userInfos,
filterFn: (item, searchText) => {
if (friendNicknameMap[item._id]) {
if (friendNicknameMap[item._id].includes(searchText)) {
return true;
}
}
if (item.nickname.includes(searchText)) {
return true;
}
return false;
},
});
return {
searchText,
setSearchText,
isSearching,
searchResult,
};
}

@ -14,7 +14,7 @@ export {
getCachedRegistryPlugins, getCachedRegistryPlugins,
getCachedUserSettings, getCachedUserSettings,
} from './cache/cache'; } from './cache/cache';
export { useCachedUserInfo, useCachedOnlineStatus } from './cache/useCache'; export { useCachedOnlineStatus } from './cache/useCache';
// components // components
export { buildPortal, DefaultEventEmitter } from './components/Portal'; export { buildPortal, DefaultEventEmitter } from './components/Portal';
@ -61,6 +61,7 @@ export { useLanguage } from './i18n/language';
export { createUseStorageState } from './hooks/factory/createUseStorageState'; export { createUseStorageState } from './hooks/factory/createUseStorageState';
export { useAvailableServices } from './hooks/model/useAvailableServices'; export { useAvailableServices } from './hooks/model/useAvailableServices';
export { useMessageNotifyEventFilter } from './hooks/model/useMessageNotifyEventFilter'; export { useMessageNotifyEventFilter } from './hooks/model/useMessageNotifyEventFilter';
export { useCachedUserInfo } from './hooks/model/useUserInfo';
export { useUserInfoList } from './hooks/model/useUserInfoList'; export { useUserInfoList } from './hooks/model/useUserInfoList';
export { useUsernames } from './hooks/model/useUsernames'; export { useUsernames } from './hooks/model/useUsernames';
export { export {
@ -82,7 +83,7 @@ export { useMemoizedFn } from './hooks/useMemoizedFn';
export { useMountedState } from './hooks/useMountedState'; export { useMountedState } from './hooks/useMountedState';
export { usePrevious } from './hooks/usePrevious'; export { usePrevious } from './hooks/usePrevious';
export { useRafState } from './hooks/useRafState'; export { useRafState } from './hooks/useRafState';
export { useSearch } from './hooks/useSearch'; export { useSearch, useUserSearch } from './hooks/useSearch';
export { useShallowObject } from './hooks/useShallowObject'; export { useShallowObject } from './hooks/useShallowObject';
export { useUpdateRef } from './hooks/useUpdateRef'; export { useUpdateRef } from './hooks/useUpdateRef';
export { useWatch } from './hooks/useWatch'; export { useWatch } from './hooks/useWatch';
@ -197,7 +198,10 @@ export { useDMConverseList } from './redux/hooks/useConverse';
export { useConverseAck } from './redux/hooks/useConverseAck'; export { useConverseAck } from './redux/hooks/useConverseAck';
export { useConverseMessage } from './redux/hooks/useConverseMessage'; export { useConverseMessage } from './redux/hooks/useConverseMessage';
export { useDMConverseName } from './redux/hooks/useDMConverseName'; export { useDMConverseName } from './redux/hooks/useDMConverseName';
export { useFriendNickname } from './redux/hooks/useFriendNickname'; export {
useFriendNickname,
useFriendNicknameMap,
} from './redux/hooks/useFriendNickname';
export { export {
useGroupInfo, useGroupInfo,
useGroupMemberIds, useGroupMemberIds,

@ -11,18 +11,9 @@ import _uniq from 'lodash/uniq';
import _flatten from 'lodash/flatten'; import _flatten from 'lodash/flatten';
import _zipObject from 'lodash/zipObject'; import _zipObject from 'lodash/zipObject';
import { t } from '../i18n'; import { t } from '../i18n';
import type { UserBaseInfo } from 'tailchat-types';
export interface UserBaseInfo { export type { UserBaseInfo };
_id: string;
email: string;
nickname: string;
discriminator: string;
avatar: string | null;
temporary: boolean;
emailVerified: boolean;
banned: boolean;
extra?: Record<string, unknown>;
}
export interface UserLoginInfo extends UserBaseInfo { export interface UserLoginInfo extends UserBaseInfo {
token: string; token: string;

@ -19,7 +19,7 @@ import {
GroupPanelType, GroupPanelType,
useHasGroupPanelPermission, useHasGroupPanelPermission,
} from 'tailchat-shared'; } from 'tailchat-shared';
import { useFriendNicknameMap } from 'tailchat-shared/redux/hooks/useFriendNickname'; import { useFriendNicknameMap } from 'tailchat-shared';
import { MembersPanel } from './MembersPanel'; import { MembersPanel } from './MembersPanel';
import { GroupPanelContainer } from './shared/GroupPanelContainer'; import { GroupPanelContainer } from './shared/GroupPanelContainer';
import { MessageSearchPanel } from '../common/MessageSearch'; import { MessageSearchPanel } from '../common/MessageSearch';

@ -13,10 +13,9 @@ import {
useGroupMemberInfos, useGroupMemberInfos,
useHasGroupPermission, useHasGroupPermission,
UserBaseInfo, UserBaseInfo,
useSearch, useUserSearch,
} from 'tailchat-shared'; } from 'tailchat-shared';
import _compact from 'lodash/compact'; import _compact from 'lodash/compact';
import { useFriendNicknameMap } from 'tailchat-shared/redux/hooks/useFriendNickname';
/** /**
* *
@ -26,7 +25,6 @@ export function useGroupMemberAction(groupId: string) {
const members = groupInfo?.members ?? []; const members = groupInfo?.members ?? [];
const roles = groupInfo?.roles ?? []; const roles = groupInfo?.roles ?? [];
const userInfos = useGroupMemberInfos(groupId); const userInfos = useGroupMemberInfos(groupId);
const friendNicknameMap = useFriendNicknameMap();
const [allowManageUser, allowManageRoles] = useHasGroupPermission(groupId, [ const [allowManageUser, allowManageRoles] = useHasGroupPermission(groupId, [
PERMISSION.core.manageUser, PERMISSION.core.manageUser,
PERMISSION.core.manageRoles, PERMISSION.core.manageRoles,
@ -37,22 +35,8 @@ export function useGroupMemberAction(groupId: string) {
userInfos userInfos
); );
const { searchText, setSearchText, isSearching, searchResult } = useSearch({ const { searchText, setSearchText, isSearching, searchResult } =
dataSource: userInfos, useUserSearch(userInfos);
filterFn: (item, searchText) => {
if (friendNicknameMap[item._id]) {
if (friendNicknameMap[item._id].includes(searchText)) {
return true;
}
}
if (item.nickname.includes(searchText)) {
return true;
}
return false;
},
});
/** /**
* *

@ -1,4 +1,4 @@
import React, { useCallback } from 'react'; import React, { useCallback, useMemo, useState } from 'react';
import { import {
createDMConverse, createDMConverse,
isValidStr, isValidStr,
@ -12,15 +12,18 @@ import {
useAsyncRequest, useAsyncRequest,
useEvent, useEvent,
useGlobalConfigStore, useGlobalConfigStore,
useUserInfoList,
useUserSearch,
userActions, userActions,
} from 'tailchat-shared'; } from 'tailchat-shared';
import { UserListItem } from '@/components/UserListItem'; import { UserListItem } from '@/components/UserListItem';
import { IconBtn } from '@/components/IconBtn'; import { IconBtn } from '@/components/IconBtn';
import { Button, Dropdown, Tooltip } from 'antd'; import { Button, Dropdown, Input, Tooltip } from 'antd';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { Problem } from '@/components/Problem'; import { Problem } from '@/components/Problem';
import { closeModal, openModal } from '@/components/Modal'; import { closeModal, openModal } from '@/components/Modal';
import { SetFriendNickname } from '@/components/modals/SetFriendNickname'; import { SetFriendNickname } from '@/components/modals/SetFriendNickname';
import { Icon } from 'tailchat-design';
/** /**
* *
@ -29,6 +32,9 @@ export const FriendList: React.FC<{
onSwitchToAddFriend: () => void; onSwitchToAddFriend: () => void;
}> = React.memo((props) => { }> = React.memo((props) => {
const friends = useAppSelector((state) => state.user.friends); const friends = useAppSelector((state) => state.user.friends);
const friendIds = useMemo(() => friends.map((f) => f.id), [friends]);
const userInfos = useUserInfoList(friendIds);
const { searchText, setSearchText, searchResult } = useUserSearch(userInfos);
const navigate = useNavigate(); const navigate = useNavigate();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const disableAddFriend = useGlobalConfigStore( const disableAddFriend = useGlobalConfigStore(
@ -91,11 +97,21 @@ export const FriendList: React.FC<{
return ( return (
<div className="py-2.5 px-5"> <div className="py-2.5 px-5">
<div>{t('好友列表')}</div> <div>{t('好友列表')}</div>
<Input
className="my-2"
placeholder={t('搜索好友')}
size="large"
prefix={<Icon fontSize={20} color="grey" icon="mdi:magnify" />}
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
/>
<div> <div>
{friends.map((item) => ( {searchResult.map((item) => (
<UserListItem <UserListItem
key={item.id} key={item._id}
userId={item.id} userId={item._id}
actions={[ actions={[
<Tooltip key="message" title={t('发送消息')}> <Tooltip key="message" title={t('发送消息')}>
<div> <div>

@ -33,6 +33,7 @@ export interface UserBaseInfo {
temporary: boolean; temporary: boolean;
type: UserType; type: UserType;
emailVerified: boolean; emailVerified: boolean;
banned: boolean;
extra?: Record<string, unknown>; extra?: Record<string, unknown>;
} }

Loading…
Cancel
Save