diff --git a/shared/model/group.ts b/shared/model/group.ts index fecc7192..2a6987f4 100644 --- a/shared/model/group.ts +++ b/shared/model/group.ts @@ -51,8 +51,15 @@ export interface GroupInfo { members: GroupMember[]; panels: GroupPanel[]; roles: GroupRole[]; + /** + * 所有人的权限列表 + * 为群组中的最低权限 + */ fallbackPermissions: string[]; - pinnedPanelId?: string; // 被钉选的面板Id + /** + * 被钉选的面板Id + */ + pinnedPanelId?: string; } /** diff --git a/web/src/components/PillTabs.less b/web/src/components/PillTabs.less index 3ca60a1b..79d4805e 100644 --- a/web/src/components/PillTabs.less +++ b/web/src/components/PillTabs.less @@ -21,10 +21,22 @@ @apply rounded bg-opacity-20; .ant-tabs-tab-btn { - @apply text-black dark:text-white ; + @apply text-black dark:text-white; } } } + + &.ant-tabs-tab-disabled { + @apply text-opacity-50; + + &.ant-tabs-tab-active { + @apply text-red-500 rounded bg-opacity-20 bg-red-500; + } + } + + .ant-tabs-tab-btn { + color: inherit; + } } } diff --git a/web/src/components/UserPicker/UserPicker.tsx b/web/src/components/UserPicker/UserPicker.tsx index a9ed8bf3..8ae37c6b 100644 --- a/web/src/components/UserPicker/UserPicker.tsx +++ b/web/src/components/UserPicker/UserPicker.tsx @@ -56,7 +56,7 @@ export const UserPicker: React.FC = React.memo((props) => {
{withSearch && ( setSearchValue(e.target.value)} @@ -78,7 +78,7 @@ export const UserPicker: React.FC = React.memo((props) => { checked={selectedIds.includes(info._id)} onChange={(e) => handleSelectUser(info._id, e.target.checked)} > -
+
diff --git a/web/src/components/modals/GroupDetail/Role/index.tsx b/web/src/components/modals/GroupDetail/Role/index.tsx index 8426e83e..8f8c0f78 100644 --- a/web/src/components/modals/GroupDetail/Role/index.tsx +++ b/web/src/components/modals/GroupDetail/Role/index.tsx @@ -1,32 +1,13 @@ -import { - DefaultFullModalInputEditorRender, - FullModalField, -} 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 { - model, - showErrorToasts, - showSuccessToasts, - t, - useGroupInfo, - useSearch, - useUserId, - useUserInfoList, -} from 'tailchat-shared'; -import { SelectGroupMember } from '../../SelectGroupMember'; -import { PermissionItem } from './PermissionItem'; +import { AllPermission } from '@/utils/role-helper'; +import React, { useMemo, useState } from 'react'; +import { t, useGroupInfo } from 'tailchat-shared'; import { RoleItem } from './RoleItem'; -import { useModifyPermission } from './useModifyPermission'; import { useRoleActions } from './useRoleActions'; -import _compact from 'lodash/compact'; +import { RoleSummary } from './tabs/summary'; +import { RolePermission } from './tabs/permission'; +import { RoleMember } from './tabs/member'; interface GroupPermissionProps { groupId: string; @@ -38,33 +19,11 @@ export const GroupRole: React.FC = React.memo((props) => { ); const groupInfo = useGroupInfo(groupId); const roles = groupInfo?.roles ?? []; - 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, - isSearching, - searchResult: filterMembers, - } = useSearch({ - dataSource: userInfoList, - filterFn: (item, searchText) => item.nickname.includes(searchText), - }); - const userId = useUserId(); + const currentRoleInfo = useMemo( () => roles.find((r) => r._id === roleId), [roles, roleId] ); - const currentRolePermissions: string[] = useMemo(() => { - if (roleId === AllPermission) { - return groupInfo?.fallbackPermissions ?? []; - } - - return currentRoleInfo?.permissions ?? []; - }, [roleId, currentRoleInfo, groupInfo]); const { loading, @@ -73,56 +32,6 @@ export const GroupRole: React.FC = React.memo((props) => { handleChangeRoleName, } = useRoleActions(groupId, roleId); - const { - isEditing, - editingPermission, - setEditingPermission, - handleSwitchPermission, - } = useModifyPermission(currentRolePermissions); - - 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( - permissionList.filter((p) => p.default === true).map((p) => p.key) - ); - }, []); - return (
@@ -157,79 +66,32 @@ export const GroupRole: React.FC = React.memo((props) => { tab={t('概述')} disabled={roleId === AllPermission} > - {/* 权限概述 */} {currentRoleInfo && ( -
- -
+ )} -
- - -
- - {/* 权限详情 */} - {permissionList.map((p) => ( - handleSwitchPermission(p.key, checked)} - /> - ))} +
- {/* 管理成员 */} -
- - } - value={searchText} - onChange={(e) => setSearchText(e.target.value)} - /> - - -
- - {(isSearching ? filterMembers : userInfoList).map((m) => ( - handleRemoveMember(m._id)} - />, - ]} + {currentRoleInfo && ( + - ))} + )}
diff --git a/web/src/components/modals/GroupDetail/Role/tabs/member.tsx b/web/src/components/modals/GroupDetail/Role/tabs/member.tsx new file mode 100644 index 00000000..49741925 --- /dev/null +++ b/web/src/components/modals/GroupDetail/Role/tabs/member.tsx @@ -0,0 +1,116 @@ +import { IconBtn } from '@/components/IconBtn'; +import { closeModal, openModal } from '@/components/Modal'; +import { SelectGroupMember } from '@/components/modals/SelectGroupMember'; +import { UserListItem } from '@/components/UserListItem'; +import { Button, Input } from 'antd'; +import React, { useCallback } from 'react'; +import { Icon } from 'tailchat-design'; +import { + model, + showErrorToasts, + showSuccessToasts, + t, + useAsyncRequest, + useGroupInfo, + useSearch, + useUserId, + useUserInfoList, +} from 'tailchat-shared'; +import _compact from 'lodash/compact'; + +interface RoleMemberProps { + groupId: string; + currentRoleInfo: model.group.GroupRole; +} +export const RoleMember: React.FC = React.memo((props) => { + const roleId = props.currentRoleInfo._id; + const userId = useUserId(); + const groupInfo = useGroupInfo(props.groupId); + const members = (groupInfo?.members ?? []).filter((m) => + m.roles.includes(roleId) + ); + const memberIds = members.map((m) => m.userId); + const userInfoList = useUserInfoList(memberIds); + const { + searchText, + setSearchText, + isSearching, + searchResult: filterMembers, + } = useSearch({ + dataSource: userInfoList, + filterFn: (item, searchText) => item.nickname.includes(searchText), + }); + + const handleAddMember = useCallback(() => { + const key = openModal( + { + try { + await model.group.appendGroupMemberRoles( + props.groupId, + selectedIds, + [props.currentRoleInfo._id] + ); + showSuccessToasts(); + closeModal(key); + } catch (err) { + showErrorToasts(err); + } + }} + /> + ); + }, [userId, props.groupId, memberIds, props.currentRoleInfo._id]); + + const [, handleRemoveMember] = useAsyncRequest( + async (memberId: string) => { + if (!props.currentRoleInfo?._id) { + showErrorToasts(t('当前没有选择任何角色组')); + return; + } + + await model.group.removeGroupMemberRoles( + props.groupId, + [memberId], + [props.currentRoleInfo._id] + ); + showSuccessToasts(); + }, + [props.groupId, props.currentRoleInfo?._id] + ); + + return ( +
+ {/* 管理成员 */} +
+ } + value={searchText} + onChange={(e) => setSearchText(e.target.value)} + /> + + +
+ + {(isSearching ? filterMembers : userInfoList).map((m) => ( + handleRemoveMember(m._id)} + />, + ]} + /> + ))} +
+ ); +}); +RoleMember.displayName = 'RoleMember'; diff --git a/web/src/components/modals/GroupDetail/Role/tabs/permission.tsx b/web/src/components/modals/GroupDetail/Role/tabs/permission.tsx new file mode 100644 index 00000000..feb91235 --- /dev/null +++ b/web/src/components/modals/GroupDetail/Role/tabs/permission.tsx @@ -0,0 +1,65 @@ +import { AllPermission, permissionList } from '@/utils/role-helper'; +import { Button } from 'antd'; +import React, { useCallback, useMemo } from 'react'; +import { model, t } from 'tailchat-shared'; +import { PermissionItem } from '../PermissionItem'; +import { useModifyPermission } from '../useModifyPermission'; + +interface RolePermissionProps { + roleId: typeof AllPermission | string; + currentRoleInfo?: model.group.GroupRole; + fallbackPermissions: string[]; + onSavePermission: (permissions: string[]) => Promise; +} +export const RolePermission: React.FC = React.memo( + (props) => { + const currentRolePermissions: string[] = useMemo(() => { + if (props.roleId === AllPermission) { + return props.fallbackPermissions; + } + + return props.currentRoleInfo?.permissions ?? []; + }, [props.roleId, props.fallbackPermissions, props.currentRoleInfo]); + + const { + isEditing, + editingPermission, + setEditingPermission, + handleSwitchPermission, + } = useModifyPermission(currentRolePermissions); + + const handleResetPermission = useCallback(() => { + setEditingPermission( + permissionList.filter((p) => p.default === true).map((p) => p.key) + ); + }, []); + + // 权限概述 + return ( +
+
+ + +
+ + {/* 权限详情 */} + {permissionList.map((p) => ( + handleSwitchPermission(p.key, checked)} + /> + ))} +
+ ); + } +); +RolePermission.displayName = 'RolePermission'; diff --git a/web/src/components/modals/GroupDetail/Role/tabs/summary.tsx b/web/src/components/modals/GroupDetail/Role/tabs/summary.tsx new file mode 100644 index 00000000..4959c629 --- /dev/null +++ b/web/src/components/modals/GroupDetail/Role/tabs/summary.tsx @@ -0,0 +1,26 @@ +import { + DefaultFullModalInputEditorRender, + FullModalField, +} from '@/components/FullModal/Field'; +import React from 'react'; +import { model, t } from 'tailchat-shared'; + +interface RoleSummaryProps { + currentRoleInfo: model.group.GroupRole; + onChangeRoleName: (roleName: string) => void; +} +export const RoleSummary: React.FC = React.memo((props) => { + // 权限概述 + return ( +
+ +
+ ); +}); +RoleSummary.displayName = 'RoleSummary';