From 1556eed93f1e1596c492b6915ab650184cded8c1 Mon Sep 17 00:00:00 2001 From: moonrailgun Date: Sun, 21 Aug 2022 01:52:21 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E8=A7=92=E8=89=B2=E7=BB=84=E6=88=90?= =?UTF-8?q?=E5=91=98=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- shared/index.tsx | 1 + shared/manager/ui.ts | 8 +++ shared/model/group.ts | 38 ++++++++++- web/src/components/UserPicker/UserPicker.tsx | 48 +++++++------ .../modals/GroupDetail/Role/index.tsx | 68 +++++++++++++++++-- .../components/modals/SelectGroupMember.tsx | 59 ++++++++++++++++ 6 files changed, 195 insertions(+), 27 deletions(-) create mode 100644 web/src/components/modals/SelectGroupMember.tsx diff --git a/shared/index.tsx b/shared/index.tsx index ce32555e..6f811262 100644 --- a/shared/index.tsx +++ b/shared/index.tsx @@ -86,6 +86,7 @@ export { showToasts, setToasts, showErrorToasts, + showSuccessToasts, showAlert, setAlert, showGlobalLoading, diff --git a/shared/manager/ui.ts b/shared/manager/ui.ts index 06a7d11f..a4c91193 100644 --- a/shared/manager/ui.ts +++ b/shared/manager/ui.ts @@ -1,3 +1,4 @@ +import { t } from '../i18n'; import { buildRegFn } from './buildRegFn'; /** @@ -23,6 +24,13 @@ export function showErrorToasts(error: unknown) { showToasts(msg, 'error'); } +/** + * 展示成功消息 + */ +export function showSuccessToasts(msg = t('操作成功')) { + showToasts(msg, 'success'); +} + interface AlertOptions { message: React.ReactNode; onConfirm?: () => void | Promise; diff --git a/shared/model/group.ts b/shared/model/group.ts index 0fb59075..fecc7192 100644 --- a/shared/model/group.ts +++ b/shared/model/group.ts @@ -7,7 +7,7 @@ export enum GroupPanelType { } export interface GroupMember { - role: string; // 角色 + roles: string[]; // 角色组 userId: string; /** * 日期字符串 禁言到xxx @@ -139,6 +139,42 @@ export async function quitGroup(groupId: string) { }); } +/** + * 更新用户所在的权限组 + * @param groupId 群组ID + * @param memberIds 成员信息 + * @param roles 权限组名 + */ +export async function appendGroupMemberRoles( + groupId: string, + memberIds: string[], + roles: string[] +) { + await request.post('/api/group/appendGroupMemberRoles', { + groupId, + memberIds, + roles, + }); +} + +/** + * 更新用户所在的权限组 + * @param groupId 群组ID + * @param memberIds 成员信息 + * @param roles 权限组名 + */ +export async function removeGroupMemberRoles( + groupId: string, + memberIds: string[], + roles: string[] +) { + await request.post('/api/group/removeGroupMemberRoles', { + groupId, + memberIds, + roles, + }); +} + /** * 创建群组邀请码 * 邀请码默认 7天有效期 diff --git a/web/src/components/UserPicker/UserPicker.tsx b/web/src/components/UserPicker/UserPicker.tsx index c79b5bd0..a9ed8bf3 100644 --- a/web/src/components/UserPicker/UserPicker.tsx +++ b/web/src/components/UserPicker/UserPicker.tsx @@ -1,4 +1,4 @@ -import { Checkbox, Input } from 'antd'; +import { Checkbox, Empty, Input } from 'antd'; import React, { useCallback, useState } from 'react'; import { t, useUserInfoList } from 'tailchat-shared'; import _take from 'lodash/take'; @@ -47,6 +47,11 @@ export const UserPicker: React.FC = React.memo((props) => { [selectedIds, onChange] ); + const matchedList = _take( + userInfoList.filter((info) => info.nickname.includes(searchValue)), + 10 + ); + return (
{withSearch && ( @@ -64,28 +69,29 @@ export const UserPicker: React.FC = React.memo((props) => { })}
- {_take( - userInfoList.filter((info) => info.nickname.includes(searchValue)), - 10 - ).map((info) => { - return ( -
- handleSelectUser(info._id, e.target.checked)} - > -
- + {matchedList.length > 0 ? ( + matchedList.map((info) => { + return ( +
+ handleSelectUser(info._id, e.target.checked)} + > +
+ -
- {info.nickname} +
+ {info.nickname} +
-
-
-
- ); - })} + +
+ ); + }) + ) : ( + + )}
); }); diff --git a/web/src/components/modals/GroupDetail/Role/index.tsx b/web/src/components/modals/GroupDetail/Role/index.tsx index 753d5894..8426e83e 100644 --- a/web/src/components/modals/GroupDetail/Role/index.tsx +++ b/web/src/components/modals/GroupDetail/Role/index.tsx @@ -4,17 +4,29 @@ import { } from '@/components/FullModal/Field'; import { IconBtn } from '@/components/IconBtn'; import { Loading } from '@/components/Loading'; +import { closeModal, openModal } from '@/components/Modal'; import { PillTabPane, PillTabs } from '@/components/PillTabs'; import { UserListItem } from '@/components/UserListItem'; import { AllPermission, permissionList } from '@/utils/role-helper'; import { Button, Input } from 'antd'; import React, { useCallback, useMemo, useState } from 'react'; import { Icon } from 'tailchat-design'; -import { t, useGroupInfo, useSearch, useUserInfoList } from 'tailchat-shared'; +import { + model, + showErrorToasts, + showSuccessToasts, + t, + useGroupInfo, + useSearch, + useUserId, + useUserInfoList, +} from 'tailchat-shared'; +import { SelectGroupMember } from '../../SelectGroupMember'; import { PermissionItem } from './PermissionItem'; import { RoleItem } from './RoleItem'; import { useModifyPermission } from './useModifyPermission'; import { useRoleActions } from './useRoleActions'; +import _compact from 'lodash/compact'; interface GroupPermissionProps { groupId: string; @@ -26,8 +38,12 @@ export const GroupRole: React.FC = React.memo((props) => { ); const groupInfo = useGroupInfo(groupId); const roles = groupInfo?.roles ?? []; - const members = (groupInfo?.members ?? []).filter((m) => m.role === roleId); - const userInfoList = useUserInfoList(members.map((m) => m.userId)); + const members = + roleId === AllPermission + ? [] + : (groupInfo?.members ?? []).filter((m) => m.roles.includes(roleId)); + const memberIds = members.map((m) => m.userId); + const userInfoList = useUserInfoList(memberIds); const { searchText, setSearchText, @@ -37,6 +53,7 @@ export const GroupRole: React.FC = React.memo((props) => { dataSource: userInfoList, filterFn: (item, searchText) => item.nickname.includes(searchText), }); + const userId = useUserId(); const currentRoleInfo = useMemo( () => roles.find((r) => r._id === roleId), [roles, roleId] @@ -63,7 +80,42 @@ export const GroupRole: React.FC = React.memo((props) => { handleSwitchPermission, } = useModifyPermission(currentRolePermissions); - const handleAddMember = useCallback(() => {}, []); + const handleAddMember = useCallback(() => { + const key = openModal( + { + if (!currentRoleInfo?._id) { + showErrorToasts(t('当前没有选择任何角色组')); + return; + } + await model.group.appendGroupMemberRoles(groupId, selectedIds, [ + currentRoleInfo._id, + ]); + showSuccessToasts(); + closeModal(key); + }} + /> + ); + }, [groupId, memberIds, currentRoleInfo?._id]); + + const handleRemoveMember = useCallback( + async (memberId: string) => { + if (!currentRoleInfo?._id) { + showErrorToasts(t('当前没有选择任何角色组')); + return; + } + + await model.group.removeGroupMemberRoles( + groupId, + [memberId], + [currentRoleInfo._id] + ); + showSuccessToasts(); + }, + [groupId, currentRoleInfo?._id] + ); const handleResetPermission = useCallback(() => { setEditingPermission( @@ -169,7 +221,13 @@ export const GroupRole: React.FC = React.memo((props) => { ]} + actions={[ + handleRemoveMember(m._id)} + />, + ]} /> ))} diff --git a/web/src/components/modals/SelectGroupMember.tsx b/web/src/components/modals/SelectGroupMember.tsx new file mode 100644 index 00000000..f63fec2f --- /dev/null +++ b/web/src/components/modals/SelectGroupMember.tsx @@ -0,0 +1,59 @@ +import { Button } from 'antd'; +import React, { useMemo, useState } from 'react'; +import { t, useAsyncFn, useGroupMemberIds } from 'tailchat-shared'; +import { ModalWrapper } from '../Modal'; +import { UserPicker } from '../UserPicker/UserPicker'; +import _without from 'lodash/without'; + +interface SelectGroupMemberProps { + /** + * 群组id + */ + groupId: string; + /** + * 排除的用户id + * 在选择好友时会进行过滤 + */ + withoutMemberIds?: string[]; + + /** + * 点击确认按钮的回调 + */ + onConfirm: (selectedUserIds: string[]) => void | Promise; +} + +/** + * 选择群组成员 + */ +export const SelectGroupMember: React.FC = React.memo( + (props) => { + const { groupId, withoutMemberIds = [], onConfirm } = props; + const [selectedUserIds, setSelectedUserIds] = useState([]); + const groupMemberIds = useGroupMemberIds(groupId); + const filterMemberIds = useMemo( + () => _without(groupMemberIds, ...withoutMemberIds), + [groupMemberIds, withoutMemberIds] + ); + + const [{ loading }, handleConfirm] = useAsyncFn(async () => { + await onConfirm(selectedUserIds); + }, [onConfirm, selectedUserIds]); + + return ( + + + +
+ +
+
+ ); + } +); +SelectGroupMember.displayName = 'SelectGroupMember';