diff --git a/client/shared/model/group.ts b/client/shared/model/group.ts index 5b23c249..d9a30932 100644 --- a/client/shared/model/group.ts +++ b/client/shared/model/group.ts @@ -6,6 +6,13 @@ export enum GroupPanelType { PLUGIN = 2, } +export const groupConfigNames = [ + // 隐藏群组成员标识位 + 'hideGroupMemberDiscriminator', +] as const; + +export type GroupConfigNames = typeof groupConfigNames[number]; + export interface GroupMember { roles: string[]; // 角色组 userId: string; @@ -51,6 +58,7 @@ export interface GroupInfo { members: GroupMember[]; panels: GroupPanel[]; roles: GroupRole[]; + config?: Partial>; /** * 所有人的权限列表 * 为群组中的最低权限 @@ -135,6 +143,24 @@ export async function modifyGroupField( }); } +/** + * 修改群组配置 + * @param groupId 群组ID + * @param configName 要修改的群组属性 + * @param configValue 要修改的属性的值 + */ +export async function modifyGroupConfig( + groupId: string, + configName: GroupConfigNames, + configValue: unknown +) { + await request.post('/api/group/updateGroupConfig', { + groupId, + configName, + configValue, + }); +} + /** * 退出群组(群组拥有者是解散群组) * 这里必须是一个socket请求因为后端需要进行房间的退出操作 diff --git a/client/shared/utils/role-helper.ts b/client/shared/utils/role-helper.ts index 84129a81..e41ac1c4 100644 --- a/client/shared/utils/role-helper.ts +++ b/client/shared/utils/role-helper.ts @@ -39,6 +39,7 @@ export const PERMISSION = { invite: 'core.invite', unlimitedInvite: 'core.unlimitedInvite', groupDetail: 'core.groupDetail', + groupConfig: 'core.groupConfig', manageUser: 'core.manageUser', managePanel: 'core.managePanel', manageInvite: 'core.manageInvite', @@ -72,6 +73,12 @@ export const getPermissionList = (): PermissionItemType[] => [ desc: t('允许成员查看群组详情'), default: false, }, + { + key: PERMISSION.core.groupConfig, + title: t('修改群组配置'), + desc: t('允许成员修改群组配置'), + default: false, + }, { key: PERMISSION.core.manageUser, title: t('允许管理用户'), diff --git a/client/web/src/components/Panel/group/MembersPanel.tsx b/client/web/src/components/Panel/group/MembersPanel.tsx index 841962e6..d99d30f9 100644 --- a/client/web/src/components/Panel/group/MembersPanel.tsx +++ b/client/web/src/components/Panel/group/MembersPanel.tsx @@ -150,6 +150,10 @@ export const MembersPanel: React.FC = React.memo((props) => { [groupId] ); + const config = groupInfo?.config ?? {}; + const hideGroupMemberDiscriminator = + config.hideGroupMemberDiscriminator ?? false; + if (userInfoList.length === 0) { return ; } @@ -230,7 +234,13 @@ export const MembersPanel: React.FC = React.memo((props) => {
} + popover={ + + } + hideDiscriminator={hideGroupMemberDiscriminator} />
@@ -240,7 +250,13 @@ export const MembersPanel: React.FC = React.memo((props) => { } + popover={ + + } + hideDiscriminator={hideGroupMemberDiscriminator} /> ); } diff --git a/client/web/src/components/UserListItem.tsx b/client/web/src/components/UserListItem.tsx index 4d7fa7e6..4e1dcd4c 100644 --- a/client/web/src/components/UserListItem.tsx +++ b/client/web/src/components/UserListItem.tsx @@ -9,9 +9,10 @@ interface UserListItemProps { userId: string; popover?: PopoverProps['content']; actions?: React.ReactElement[]; + hideDiscriminator?: boolean; } export const UserListItem: React.FC = React.memo((props) => { - const { actions = [] } = props; + const { actions = [], hideDiscriminator = false } = props; const userInfo = useCachedUserInfo(props.userId); const [isOnline] = useCachedOnlineStatus([props.userId]); const userName = userInfo.nickname; @@ -38,9 +39,11 @@ export const UserListItem: React.FC = React.memo((props) => {
{userName} - - #{userInfo.discriminator} - + {!hideDiscriminator && ( + + #{userInfo.discriminator} + + )}
{actions}
diff --git a/client/web/src/components/modals/GroupDetail/Config.tsx b/client/web/src/components/modals/GroupDetail/Config.tsx new file mode 100644 index 00000000..d4b5d08b --- /dev/null +++ b/client/web/src/components/modals/GroupDetail/Config.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { + model, + showSuccessToasts, + t, + useAsyncRequest, + useGroupInfo, +} from 'tailchat-shared'; +import { Loading } from '@/components/Loading'; +import { FullModalField } from '@/components/FullModal/Field'; +import { FullModalCommonTitle } from '@/components/FullModal/CommonTitle'; +import { Switch } from 'antd'; + +export const GroupConfig: React.FC<{ + groupId: string; +}> = React.memo((props) => { + const groupId = props.groupId; + const groupInfo = useGroupInfo(groupId); + + const [{ loading }, handleModifyConfig] = useAsyncRequest( + async (configName: model.group.GroupConfigNames, configValue: any) => { + await model.group.modifyGroupConfig(groupId, configName, configValue); + }, + [groupId] + ); + + if (!groupInfo) { + return ; + } + + const config = groupInfo.config ?? {}; + + return ( +
+ {t('群组配置')} + + + handleModifyConfig('hideGroupMemberDiscriminator', checked) + } + /> + } + /> +
+ ); +}); +GroupConfig.displayName = 'GroupConfig'; diff --git a/client/web/src/components/modals/GroupDetail/index.tsx b/client/web/src/components/modals/GroupDetail/index.tsx index 6ea8e0a2..3b106ca3 100644 --- a/client/web/src/components/modals/GroupDetail/index.tsx +++ b/client/web/src/components/modals/GroupDetail/index.tsx @@ -13,6 +13,7 @@ import { GroupPanel } from './Panel'; import { GroupRole } from './Role'; import { GroupSummary } from './Summary'; import _compact from 'lodash/compact'; +import { GroupConfig } from './Config'; interface SettingsViewProps { groupId: string; @@ -28,12 +29,17 @@ export const GroupDetail: React.FC = React.memo((props) => { }, [props.onClose] ); - const [allowManagePanel, allowManageInvite, allowManageRoles] = - useHasGroupPermission(groupId, [ - PERMISSION.core.managePanel, - PERMISSION.core.manageInvite, - PERMISSION.core.manageRoles, - ]); + const [ + allowManageConfig, + allowManagePanel, + allowManageInvite, + allowManageRoles, + ] = useHasGroupPermission(groupId, [ + PERMISSION.core.groupConfig, + PERMISSION.core.managePanel, + PERMISSION.core.manageInvite, + PERMISSION.core.manageRoles, + ]); const menu: SidebarViewMenuType[] = useMemo(() => { // 内置 @@ -47,6 +53,11 @@ export const GroupDetail: React.FC = React.memo((props) => { title: t('概述'), content: , }, + allowManageConfig && { + type: 'item', + title: t('配置'), + content: , + }, allowManagePanel && { type: 'item', title: t('面板'), diff --git a/client/web/src/components/popover/GroupUserPopover.tsx b/client/web/src/components/popover/GroupUserPopover.tsx index 76086922..45008f7c 100644 --- a/client/web/src/components/popover/GroupUserPopover.tsx +++ b/client/web/src/components/popover/GroupUserPopover.tsx @@ -7,8 +7,9 @@ import { UserProfileContainer } from '../UserProfileContainer'; export const GroupUserPopover: React.FC<{ userInfo: UserBaseInfo; + hideDiscriminator?: boolean; }> = React.memo((props) => { - const { userInfo } = props; + const { userInfo, hideDiscriminator = false } = props; const userExtra = userInfo.extra ?? {}; useEffect(() => { @@ -24,7 +25,9 @@ export const GroupUserPopover: React.FC<{
{userInfo.nickname} - #{userInfo.discriminator} + {!hideDiscriminator && ( + #{userInfo.discriminator} + )}
diff --git a/server/models/group/group.ts b/server/models/group/group.ts index a0e3e19c..84353476 100644 --- a/server/models/group/group.ts +++ b/server/models/group/group.ts @@ -138,6 +138,12 @@ export class Group extends TimeStamps implements Base { }) fallbackPermissions: string[]; + /** + * 群组的配置信息 + */ + @prop({ default: () => ({}) }) + config: object; + /** * 创建群组 */ diff --git a/server/packages/sdk/src/services/lib/role.ts b/server/packages/sdk/src/services/lib/role.ts index 2fbb8e44..0f6677d1 100644 --- a/server/packages/sdk/src/services/lib/role.ts +++ b/server/packages/sdk/src/services/lib/role.ts @@ -8,6 +8,7 @@ export const PERMISSION = { invite: 'core.invite', unlimitedInvite: 'core.unlimitedInvite', groupDetail: 'core.groupDetail', + groupConfig: 'core.groupConfig', manageUser: 'core.manageUser', managePanel: 'core.managePanel', manageInvite: 'core.manageInvite', diff --git a/server/services/core/group/group.service.ts b/server/services/core/group/group.service.ts index db0932fc..de854dc7 100644 --- a/server/services/core/group/group.service.ts +++ b/server/services/core/group/group.service.ts @@ -66,6 +66,13 @@ class GroupService extends TcService { fieldValue: 'any', }, }); + this.registerAction('updateGroupConfig', this.updateGroupConfig, { + params: { + groupId: 'string', + configName: 'string', + configValue: 'any', + }, + }); this.registerAction('isGroupOwner', this.isGroupOwner, { params: { groupId: 'string', @@ -353,6 +360,37 @@ class GroupService extends TcService { this.notifyGroupInfoUpdate(ctx, group); } + /** + * 修改群组配置 + */ + async updateGroupConfig( + ctx: TcContext<{ + groupId: string; + configName: string; + configValue: unknown; + }> + ) { + const { groupId, configName, configValue } = ctx.params; + const userId = ctx.meta.userId; + const t = ctx.meta.t; + + const [hasPermission] = await call(ctx).checkUserPermissions( + groupId, + userId, + [PERMISSION.core.groupConfig] + ); + + if (!hasPermission) { + throw new NoPermissionError(t('没有操作权限')); + } + + const group = await this.adapter.model.findById(groupId).exec(); + group.config[configName] = configValue; + await group.save(); + + this.notifyGroupInfoUpdate(ctx, group); + } + /** * 检测用户是否为群组所有者 */