新增IM聊天群管理接口,包括群聊详情、添加成员、移除成员、修改群名称、转让群主、退出群聊及解散群聊功能,提升群聊管理体验。
This commit is contained in:
@@ -12,10 +12,12 @@ import org.jeecg.common.system.vo.LoginUser;
|
||||
import org.jeecg.common.util.TokenUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.im.dto.SysImCreateGroupDTO;
|
||||
import org.jeecg.modules.im.dto.SysImGroupMembersDTO;
|
||||
import org.jeecg.modules.im.dto.SysImSendMessageDTO;
|
||||
import org.jeecg.modules.im.service.ISysImChatService;
|
||||
import org.jeecg.modules.im.vo.SysImContactVO;
|
||||
import org.jeecg.modules.im.vo.SysImConversationVO;
|
||||
import org.jeecg.modules.im.vo.SysImGroupDetailVO;
|
||||
import org.jeecg.modules.im.vo.SysImMessageVO;
|
||||
import org.jeecg.modules.system.entity.SysUser;
|
||||
import org.jeecg.modules.system.service.ISysUserService;
|
||||
@@ -155,4 +157,69 @@ public class SysImChatController {
|
||||
return Result.OK(imChatService.createGroupConversation(user.getId(), resolveTenantId(user, request), user.getOrgCode(), dto));
|
||||
}
|
||||
//update-end---author:xsl ---date:20260528 for:【IM聊天-OA】群聊接口-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群管理接口-----------
|
||||
@Operation(summary = "IM聊天-群聊详情")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@GetMapping("/group/detail")
|
||||
public Result<SysImGroupDetailVO> groupDetail(@RequestParam(name = "conversationId") String conversationId) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.getGroupDetail(user.getId(), conversationId));
|
||||
}
|
||||
|
||||
@Operation(summary = "IM聊天-添加群成员")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@PostMapping("/group/addMembers")
|
||||
public Result<SysImConversationVO> addGroupMembers(@RequestBody SysImGroupMembersDTO dto, HttpServletRequest request) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.addGroupMembers(user.getId(), resolveTenantId(user, request), user.getOrgCode(), dto));
|
||||
}
|
||||
|
||||
@Operation(summary = "IM聊天-移除群成员")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@PostMapping("/group/removeMember")
|
||||
public Result<String> removeGroupMember(@RequestParam(name = "conversationId") String conversationId,
|
||||
@RequestParam(name = "memberUserId") String memberUserId) {
|
||||
LoginUser user = currentUser();
|
||||
imChatService.removeGroupMember(user.getId(), conversationId, memberUserId);
|
||||
return Result.OK("移除成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "IM聊天-修改群名称")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@PostMapping("/group/rename")
|
||||
public Result<SysImConversationVO> renameGroup(@RequestParam(name = "conversationId") String conversationId,
|
||||
@RequestParam(name = "groupName") String groupName) {
|
||||
LoginUser user = currentUser();
|
||||
return Result.OK(imChatService.renameGroup(user.getId(), conversationId, groupName));
|
||||
}
|
||||
|
||||
@Operation(summary = "IM聊天-转让群主")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@PostMapping("/group/transfer")
|
||||
public Result<String> transferGroupOwner(@RequestParam(name = "conversationId") String conversationId,
|
||||
@RequestParam(name = "newOwnerId") String newOwnerId) {
|
||||
LoginUser user = currentUser();
|
||||
imChatService.transferGroupOwner(user.getId(), conversationId, newOwnerId);
|
||||
return Result.OK("转让成功");
|
||||
}
|
||||
|
||||
@Operation(summary = "IM聊天-退出群聊")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@PostMapping("/group/quit")
|
||||
public Result<String> quitGroup(@RequestParam(name = "conversationId") String conversationId) {
|
||||
LoginUser user = currentUser();
|
||||
imChatService.quitGroup(user.getId(), conversationId);
|
||||
return Result.OK("已退出群聊");
|
||||
}
|
||||
|
||||
@Operation(summary = "IM聊天-解散群聊")
|
||||
@RequiresPermissions("sys:im:chat:list")
|
||||
@PostMapping("/group/dismiss")
|
||||
public Result<String> dismissGroup(@RequestParam(name = "conversationId") String conversationId) {
|
||||
LoginUser user = currentUser();
|
||||
imChatService.dismissGroup(user.getId(), conversationId);
|
||||
return Result.OK("群聊已解散");
|
||||
}
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群管理接口-----------
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
package org.jeecg.modules.im.dto;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IM 群聊添加成员
|
||||
*/
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-添加群成员-----------
|
||||
@Data
|
||||
@Schema(description = "IM群聊添加成员")
|
||||
public class SysImGroupMembersDTO {
|
||||
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
|
||||
@Schema(description = "新增成员用户ID")
|
||||
private List<String> memberUserIds;
|
||||
}
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-添加群成员-----------
|
||||
@@ -5,12 +5,15 @@ package org.jeecg.modules.im.service;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
|
||||
import org.jeecg.modules.im.dto.SysImCreateGroupDTO;
|
||||
import org.jeecg.modules.im.dto.SysImGroupMembersDTO;
|
||||
import org.jeecg.modules.im.dto.SysImSendMessageDTO;
|
||||
|
||||
import org.jeecg.modules.im.vo.SysImContactVO;
|
||||
|
||||
import org.jeecg.modules.im.vo.SysImConversationVO;
|
||||
|
||||
import org.jeecg.modules.im.vo.SysImGroupDetailVO;
|
||||
|
||||
import org.jeecg.modules.im.vo.SysImMessageVO;
|
||||
|
||||
|
||||
@@ -53,5 +56,28 @@ public interface ISysImChatService {
|
||||
|
||||
List<SysImContactVO> listDeptMembers(String userId, Integer tenantId, String orgCode, String keyword);
|
||||
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群管理接口-----------
|
||||
/** 群聊详情(含成员列表,区分群主) */
|
||||
SysImGroupDetailVO getGroupDetail(String userId, String conversationId);
|
||||
|
||||
/** 添加群成员(所有群成员可操作) */
|
||||
SysImConversationVO addGroupMembers(String userId, Integer tenantId, String orgCode, SysImGroupMembersDTO dto);
|
||||
|
||||
/** 移除群成员(仅群主) */
|
||||
void removeGroupMember(String userId, String conversationId, String memberUserId);
|
||||
|
||||
/** 修改群名称(仅群主) */
|
||||
SysImConversationVO renameGroup(String userId, String conversationId, String groupName);
|
||||
|
||||
/** 转让群主(仅群主) */
|
||||
void transferGroupOwner(String userId, String conversationId, String newOwnerId);
|
||||
|
||||
/** 退出群聊(非群主成员) */
|
||||
void quitGroup(String userId, String conversationId);
|
||||
|
||||
/** 解散群聊(仅群主) */
|
||||
void dismissGroup(String userId, String conversationId);
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群管理接口-----------
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ import org.jeecg.common.exception.JeecgBootException;
|
||||
import org.jeecg.common.util.DateUtils;
|
||||
import org.jeecg.common.util.oConvertUtils;
|
||||
import org.jeecg.modules.im.dto.SysImCreateGroupDTO;
|
||||
import org.jeecg.modules.im.dto.SysImGroupMembersDTO;
|
||||
import org.jeecg.modules.im.dto.SysImSendMessageDTO;
|
||||
import org.jeecg.modules.im.entity.SysImConversation;
|
||||
import org.jeecg.modules.im.entity.SysImConversationMember;
|
||||
@@ -20,6 +21,8 @@ import org.jeecg.modules.im.mapper.SysImMessageMapper;
|
||||
import org.jeecg.modules.im.service.ISysImChatService;
|
||||
import org.jeecg.modules.im.vo.SysImContactVO;
|
||||
import org.jeecg.modules.im.vo.SysImConversationVO;
|
||||
import org.jeecg.modules.im.vo.SysImGroupDetailVO;
|
||||
import org.jeecg.modules.im.vo.SysImGroupMemberVO;
|
||||
import org.jeecg.modules.im.vo.SysImMessageVO;
|
||||
import org.jeecg.modules.message.websocket.WebSocket;
|
||||
import org.jeecg.modules.system.entity.SysDepart;
|
||||
@@ -198,6 +201,193 @@ public class SysImChatServiceImpl implements ISysImChatService {
|
||||
}
|
||||
//update-end---author:xsl ---date:20260528 for:【IM聊天-OA】群聊会话列表与创建-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群管理-----------
|
||||
private static final int GROUP_MEMBER_MAX = 50;
|
||||
|
||||
@Override
|
||||
public SysImGroupDetailVO getGroupDetail(String userId, String conversationId) {
|
||||
SysImConversation conversation = assertGroupConversation(userId, conversationId);
|
||||
List<SysImConversationMember> members = memberMapper.selectList(new LambdaQueryWrapper<SysImConversationMember>()
|
||||
.eq(SysImConversationMember::getConversationId, conversationId)
|
||||
.orderByAsc(SysImConversationMember::getCreateTime));
|
||||
List<String> memberUserIds = members.stream()
|
||||
.map(SysImConversationMember::getUserId)
|
||||
.filter(oConvertUtils::isNotEmpty)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
Map<String, SysUser> userMap = new HashMap<>(memberUserIds.size());
|
||||
if (!memberUserIds.isEmpty()) {
|
||||
List<SysUser> users = userMapper.selectBatchIds(memberUserIds);
|
||||
if (users != null) {
|
||||
for (SysUser user : users) {
|
||||
userMap.put(user.getId(), user);
|
||||
}
|
||||
}
|
||||
}
|
||||
String ownerId = conversation.getOwnerId();
|
||||
List<SysImGroupMemberVO> memberVoList = new ArrayList<>(members.size());
|
||||
for (SysImConversationMember member : members) {
|
||||
SysUser user = userMap.get(member.getUserId());
|
||||
SysImGroupMemberVO memberVo = new SysImGroupMemberVO();
|
||||
memberVo.setUserId(member.getUserId());
|
||||
memberVo.setOwner(member.getUserId() != null && member.getUserId().equals(ownerId));
|
||||
if (user != null) {
|
||||
memberVo.setRealname(user.getRealname());
|
||||
memberVo.setUsername(user.getUsername());
|
||||
memberVo.setAvatar(user.getAvatar());
|
||||
}
|
||||
memberVoList.add(memberVo);
|
||||
}
|
||||
// 群主排在最前
|
||||
memberVoList.sort(Comparator.comparingInt(item -> Boolean.TRUE.equals(item.getOwner()) ? 0 : 1));
|
||||
SysImGroupDetailVO detail = new SysImGroupDetailVO();
|
||||
detail.setConversationId(conversation.getId());
|
||||
detail.setGroupName(conversation.getGroupName());
|
||||
detail.setOwnerId(ownerId);
|
||||
detail.setMemberCount(memberVoList.size());
|
||||
detail.setOwner(userId.equals(ownerId));
|
||||
detail.setMembers(memberVoList);
|
||||
return detail;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SysImConversationVO addGroupMembers(String userId, Integer tenantId, String orgCode, SysImGroupMembersDTO dto) {
|
||||
if (dto == null || oConvertUtils.isEmpty(dto.getConversationId())) {
|
||||
throw new JeecgBootException("会话不存在");
|
||||
}
|
||||
SysImConversation conversation = assertGroupConversation(userId, dto.getConversationId());
|
||||
if (dto.getMemberUserIds() == null || dto.getMemberUserIds().isEmpty()) {
|
||||
throw new JeecgBootException("请选择要添加的成员");
|
||||
}
|
||||
// 已在群成员
|
||||
Set<String> existIds = memberMapper.selectList(new LambdaQueryWrapper<SysImConversationMember>()
|
||||
.eq(SysImConversationMember::getConversationId, conversation.getId()))
|
||||
.stream().map(SysImConversationMember::getUserId).collect(Collectors.toSet());
|
||||
// 去重并过滤已存在成员
|
||||
List<String> toAdd = new ArrayList<>();
|
||||
Set<String> seen = new LinkedHashSet<>();
|
||||
for (String memberId : dto.getMemberUserIds()) {
|
||||
if (oConvertUtils.isEmpty(memberId) || existIds.contains(memberId) || !seen.add(memberId)) {
|
||||
continue;
|
||||
}
|
||||
toAdd.add(memberId);
|
||||
}
|
||||
if (toAdd.isEmpty()) {
|
||||
throw new JeecgBootException("所选成员已在群内");
|
||||
}
|
||||
if (existIds.size() + toAdd.size() > GROUP_MEMBER_MAX) {
|
||||
throw new JeecgBootException("群成员不能超过" + GROUP_MEMBER_MAX + "人");
|
||||
}
|
||||
// 校验同租户、同部门
|
||||
for (String memberId : toAdd) {
|
||||
validateTenantChat(userId, tenantId, orgCode, memberId);
|
||||
}
|
||||
Date now = new Date();
|
||||
for (String memberId : toAdd) {
|
||||
createMember(conversation.getId(), memberId, now);
|
||||
}
|
||||
return buildGroupConversationVo(conversation, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void removeGroupMember(String userId, String conversationId, String memberUserId) {
|
||||
SysImConversation conversation = assertGroupConversation(userId, conversationId);
|
||||
assertGroupOwner(userId, conversation);
|
||||
if (oConvertUtils.isEmpty(memberUserId)) {
|
||||
throw new JeecgBootException("请选择要移除的成员");
|
||||
}
|
||||
if (memberUserId.equals(conversation.getOwnerId())) {
|
||||
throw new JeecgBootException("群主不能被移除");
|
||||
}
|
||||
memberMapper.delete(new LambdaQueryWrapper<SysImConversationMember>()
|
||||
.eq(SysImConversationMember::getConversationId, conversationId)
|
||||
.eq(SysImConversationMember::getUserId, memberUserId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public SysImConversationVO renameGroup(String userId, String conversationId, String groupName) {
|
||||
SysImConversation conversation = assertGroupConversation(userId, conversationId);
|
||||
assertGroupOwner(userId, conversation);
|
||||
if (oConvertUtils.isEmpty(groupName)) {
|
||||
throw new JeecgBootException("群名称不能为空");
|
||||
}
|
||||
String name = groupName.trim();
|
||||
if (name.length() > 30) {
|
||||
throw new JeecgBootException("群名称不能超过30字");
|
||||
}
|
||||
conversation.setGroupName(name);
|
||||
conversation.setUpdateBy(userId);
|
||||
conversation.setUpdateTime(new Date());
|
||||
conversationMapper.updateById(conversation);
|
||||
return buildGroupConversationVo(conversation, userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void transferGroupOwner(String userId, String conversationId, String newOwnerId) {
|
||||
SysImConversation conversation = assertGroupConversation(userId, conversationId);
|
||||
assertGroupOwner(userId, conversation);
|
||||
if (oConvertUtils.isEmpty(newOwnerId)) {
|
||||
throw new JeecgBootException("请选择新群主");
|
||||
}
|
||||
if (newOwnerId.equals(userId)) {
|
||||
throw new JeecgBootException("不能转让给自己");
|
||||
}
|
||||
if (getMember(newOwnerId, conversationId) == null) {
|
||||
throw new JeecgBootException("新群主必须是群成员");
|
||||
}
|
||||
conversation.setOwnerId(newOwnerId);
|
||||
conversation.setUpdateBy(userId);
|
||||
conversation.setUpdateTime(new Date());
|
||||
conversationMapper.updateById(conversation);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void quitGroup(String userId, String conversationId) {
|
||||
SysImConversation conversation = assertGroupConversation(userId, conversationId);
|
||||
if (userId.equals(conversation.getOwnerId())) {
|
||||
throw new JeecgBootException("群主不能退出群聊,请先转让群主或解散群聊");
|
||||
}
|
||||
memberMapper.delete(new LambdaQueryWrapper<SysImConversationMember>()
|
||||
.eq(SysImConversationMember::getConversationId, conversationId)
|
||||
.eq(SysImConversationMember::getUserId, userId));
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void dismissGroup(String userId, String conversationId) {
|
||||
SysImConversation conversation = assertGroupConversation(userId, conversationId);
|
||||
assertGroupOwner(userId, conversation);
|
||||
memberMapper.delete(new LambdaQueryWrapper<SysImConversationMember>()
|
||||
.eq(SysImConversationMember::getConversationId, conversationId));
|
||||
conversationMapper.deleteById(conversationId);
|
||||
}
|
||||
|
||||
/** 校验会话存在、为群聊且当前用户是群成员 */
|
||||
private SysImConversation assertGroupConversation(String userId, String conversationId) {
|
||||
if (oConvertUtils.isEmpty(conversationId)) {
|
||||
throw new JeecgBootException("会话不存在");
|
||||
}
|
||||
SysImConversation conversation = conversationMapper.selectById(conversationId);
|
||||
if (conversation == null || !CONV_TYPE_GROUP.equals(conversation.getConvType())) {
|
||||
throw new JeecgBootException("群聊不存在");
|
||||
}
|
||||
assertMember(userId, conversationId);
|
||||
return conversation;
|
||||
}
|
||||
|
||||
/** 校验当前用户是群主 */
|
||||
private void assertGroupOwner(String userId, SysImConversation conversation) {
|
||||
if (!userId.equals(conversation.getOwnerId())) {
|
||||
throw new JeecgBootException("仅群主可执行该操作");
|
||||
}
|
||||
}
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群管理-----------
|
||||
|
||||
//update-begin---author:cursor ---date:20260528 for:【IM聊天-OA】消息分页-----------
|
||||
@Override
|
||||
public IPage<SysImMessageVO> listMessages(String userId, String conversationId, Integer pageNo, Integer pageSize, String startTime, String beforeTime) {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package org.jeecg.modules.im.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* IM 群聊详情(群设置)
|
||||
*/
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群详情-----------
|
||||
@Data
|
||||
@Schema(description = "IM群聊详情")
|
||||
public class SysImGroupDetailVO {
|
||||
|
||||
@Schema(description = "会话ID")
|
||||
private String conversationId;
|
||||
@Schema(description = "群名称")
|
||||
private String groupName;
|
||||
@Schema(description = "群主用户ID")
|
||||
private String ownerId;
|
||||
@Schema(description = "群成员数")
|
||||
private Integer memberCount;
|
||||
@Schema(description = "当前用户是否群主")
|
||||
private Boolean owner;
|
||||
@Schema(description = "群成员列表")
|
||||
private List<SysImGroupMemberVO> members;
|
||||
}
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群详情-----------
|
||||
@@ -0,0 +1,25 @@
|
||||
package org.jeecg.modules.im.vo;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* IM 群成员
|
||||
*/
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群成员展示-----------
|
||||
@Data
|
||||
@Schema(description = "IM群成员")
|
||||
public class SysImGroupMemberVO {
|
||||
|
||||
@Schema(description = "用户ID")
|
||||
private String userId;
|
||||
@Schema(description = "姓名")
|
||||
private String realname;
|
||||
@Schema(description = "账号")
|
||||
private String username;
|
||||
@Schema(description = "头像")
|
||||
private String avatar;
|
||||
@Schema(description = "是否群主")
|
||||
private Boolean owner;
|
||||
}
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-群成员展示-----------
|
||||
@@ -131,6 +131,9 @@
|
||||
</button>
|
||||
<template #overlay>
|
||||
<a-menu @click="handleSettingsMenuClick">
|
||||
<!--update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群聊设置入口------------->
|
||||
<a-menu-item v-if="isGroupChat" key="groupSettings">群设置</a-menu-item>
|
||||
<!--update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群聊设置入口----------->
|
||||
<a-menu-item key="chatSettings">聊天设置</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
@@ -214,6 +217,10 @@
|
||||
<ImChatSettingsModal @register="registerSettingsModal" @saved="onChatSettingsSaved" />
|
||||
<ImPageListPickModal @register="registerListPickModal" @confirm="handleListRowsSend" />
|
||||
<ImCreateGroupModal @register="registerCreateGroupModal" @success="handleGroupCreated" />
|
||||
<!--update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置抽屉------------->
|
||||
<ImGroupSettingDrawer @register="registerGroupSettingDrawer" @changed="handleGroupSettingChanged" @exited="handleGroupExited" />
|
||||
<!--update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置抽屉----------->
|
||||
|
||||
<a-modal :open="previewVisible" :footer="null" width="720px" @cancel="closeImagePreview">
|
||||
<img alt="图片预览" style="width: 100%" :src="previewImageUrl" />
|
||||
</a-modal>
|
||||
@@ -243,6 +250,10 @@
|
||||
import ImPageListPickModal from './ImPageListPickModal.vue';
|
||||
import ImBizRecordMessageContent from './ImBizRecordMessageContent.vue';
|
||||
import ImCreateGroupModal from './ImCreateGroupModal.vue';
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置抽屉-----------
|
||||
import ImGroupSettingDrawer from './ImGroupSettingDrawer.vue';
|
||||
import { useDrawer } from '/@/components/Drawer';
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置抽屉-----------
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { buildImBizRecordPayload, parseImBizRecordPayload, serializeImBizRecordPayload } from './imBizRecordMessage';
|
||||
@@ -499,6 +510,9 @@
|
||||
const [registerListPickModal, { openModal: openListPickModal }] = useModal();
|
||||
const [registerCreateGroupModal, { openModal: openCreateGroupModal }] = useModal();
|
||||
//update-end---author:xsl ---date:20260528 for:【IM聊天-OA】点击功能名气泡选择列表明细发送-----------
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置抽屉-----------
|
||||
const [registerGroupSettingDrawer, { openDrawer: openGroupSettingDrawer, closeDrawer: closeGroupSettingDrawer }] = useDrawer();
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置抽屉-----------
|
||||
const defaultHistoryDays = ref(getImDefaultHistoryDays());
|
||||
const currentUserId = computed(() => userStore.getUserInfo?.id || '');
|
||||
const deptLabel = computed(() => {
|
||||
@@ -715,9 +729,60 @@
|
||||
function handleSettingsMenuClick({ key }: { key: string }) {
|
||||
if (key === 'chatSettings') {
|
||||
openSettingsModal(true, {});
|
||||
return;
|
||||
}
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】打开群设置抽屉-----------
|
||||
if (key === 'groupSettings') {
|
||||
if (!activeGroup.value?.conversationId) {
|
||||
return;
|
||||
}
|
||||
openGroupSettingDrawer(true, { conversationId: activeGroup.value.conversationId });
|
||||
}
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】打开群设置抽屉-----------
|
||||
}
|
||||
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置变更/退出回调-----------
|
||||
/** 群信息变更(改名/加人/踢人/转让):同步标题、成员数,并刷新群列表 */
|
||||
async function handleGroupSettingChanged(payload: { conversationId: string; groupName?: string; memberCount?: number }) {
|
||||
if (!payload?.conversationId) {
|
||||
return;
|
||||
}
|
||||
patchGroup(payload.conversationId, {
|
||||
groupName: payload.groupName,
|
||||
memberCount: payload.memberCount,
|
||||
}, { moveToTop: false });
|
||||
if (activeGroup.value?.conversationId === payload.conversationId) {
|
||||
activeGroup.value = {
|
||||
...activeGroup.value,
|
||||
groupName: payload.groupName ?? activeGroup.value.groupName,
|
||||
memberCount: payload.memberCount ?? activeGroup.value.memberCount,
|
||||
};
|
||||
}
|
||||
// 后台静默刷新群列表,保证成员数等准确
|
||||
await loadGroups(true);
|
||||
}
|
||||
|
||||
/** 退出或解散群聊:从列表移除,若为当前会话则清空聊天区 */
|
||||
async function handleGroupExited(conversationId: string) {
|
||||
if (!conversationId) {
|
||||
return;
|
||||
}
|
||||
groupList.value = groupList.value.filter((item) => item.conversationId !== conversationId);
|
||||
resetGroupUnread(conversationId);
|
||||
syncAllUnread();
|
||||
if (activeGroup.value?.conversationId === conversationId) {
|
||||
activeGroup.value = null;
|
||||
activeChatType.value = '';
|
||||
activeConversationId.value = '';
|
||||
setImActiveConversationId('');
|
||||
messageList.value = [];
|
||||
draft.value = '';
|
||||
clearImActiveSession();
|
||||
}
|
||||
closeGroupSettingDrawer();
|
||||
}
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置变更/退出回调-----------
|
||||
|
||||
function onChatSettingsSaved() {
|
||||
defaultHistoryDays.value = getImDefaultHistoryDays();
|
||||
if (activeConversationId.value) {
|
||||
|
||||
164
jeecgboot-vue3/src/views/system/im/ImGroupAddMemberModal.vue
Normal file
164
jeecgboot-vue3/src/views/system/im/ImGroupAddMemberModal.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<!--update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-添加群成员弹窗----------->
|
||||
<template>
|
||||
<BasicModal v-bind="$attrs" title="添加群成员" :width="520" @register="registerModal" @ok="handleSubmit">
|
||||
<a-form layout="vertical">
|
||||
<a-form-item label="选择成员" required>
|
||||
<a-input
|
||||
v-model:value="memberKeyword"
|
||||
placeholder="搜索同事"
|
||||
allow-clear
|
||||
size="small"
|
||||
class="member-search"
|
||||
@pressEnter="loadMembers"
|
||||
/>
|
||||
<a-spin :spinning="loading">
|
||||
<div class="member-list">
|
||||
<a-checkbox-group v-model:value="checkedMemberIds" class="member-checkbox-group">
|
||||
<div v-for="item in filteredMembers" :key="item.id" class="member-row">
|
||||
<a-checkbox :value="item.id">
|
||||
<div class="member-info">
|
||||
<a-avatar :size="28" :src="getAvatarUrl(item.avatar)">
|
||||
{{ (item.realname || item.username || '?').slice(0, 1) }}
|
||||
</a-avatar>
|
||||
<span class="member-name">{{ item.realname || item.username }}</span>
|
||||
</div>
|
||||
</a-checkbox>
|
||||
</div>
|
||||
</a-checkbox-group>
|
||||
<a-empty v-if="!filteredMembers.length" description="暂无可添加的同事" />
|
||||
</div>
|
||||
</a-spin>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</BasicModal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { BasicModal, useModalInner } from '/@/components/Modal';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import { useUserStore } from '/@/store/modules/user';
|
||||
import { addGroupMembers, fetchDeptMembers } from './im.api';
|
||||
|
||||
defineOptions({ name: 'ImGroupAddMemberModal' });
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
const emit = defineEmits<{
|
||||
success: [];
|
||||
}>();
|
||||
|
||||
const { createMessage } = useMessage();
|
||||
const memberKeyword = ref('');
|
||||
const checkedMemberIds = ref<string[]>([]);
|
||||
const members = ref<Recordable[]>([]);
|
||||
const loading = ref(false);
|
||||
const conversationId = ref('');
|
||||
// 已在群成员(用于过滤候选)
|
||||
const existMemberIds = ref<string[]>([]);
|
||||
|
||||
const filteredMembers = computed(() => {
|
||||
const keyword = memberKeyword.value.trim().toLowerCase();
|
||||
if (!keyword) {
|
||||
return members.value;
|
||||
}
|
||||
return members.value.filter((item) => {
|
||||
const name = `${item.realname || ''}${item.username || ''}`.toLowerCase();
|
||||
return name.includes(keyword);
|
||||
});
|
||||
});
|
||||
|
||||
/** 过滤掉当前用户与已在群成员 */
|
||||
function resolveSelectableMembers(source: Recordable[]) {
|
||||
const currentUserId = userStore.getUserInfo?.id || '';
|
||||
const excludeSet = new Set([currentUserId, ...existMemberIds.value]);
|
||||
return source.filter((item) => item.id && !excludeSet.has(item.id));
|
||||
}
|
||||
|
||||
const [registerModal, { setModalProps, closeModal }] = useModalInner(
|
||||
async (data?: { conversationId?: string; existMemberIds?: string[] }) => {
|
||||
memberKeyword.value = '';
|
||||
checkedMemberIds.value = [];
|
||||
conversationId.value = data?.conversationId || '';
|
||||
existMemberIds.value = data?.existMemberIds || [];
|
||||
setModalProps({ confirmLoading: false });
|
||||
members.value = [];
|
||||
await loadMembers();
|
||||
},
|
||||
);
|
||||
|
||||
function getAvatarUrl(avatar?: string) {
|
||||
return avatar ? getFileAccessHttpUrl(avatar) : '';
|
||||
}
|
||||
|
||||
async function loadMembers() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const list = ((await fetchDeptMembers(memberKeyword.value || undefined)) || []) as Recordable[];
|
||||
members.value = resolveSelectableMembers(list);
|
||||
} catch {
|
||||
createMessage.error('加载同事列表失败,请稍后重试');
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleSubmit() {
|
||||
if (!conversationId.value) {
|
||||
return;
|
||||
}
|
||||
if (!checkedMemberIds.value.length) {
|
||||
createMessage.warning('请至少选择1名同事');
|
||||
return;
|
||||
}
|
||||
setModalProps({ confirmLoading: true });
|
||||
try {
|
||||
await addGroupMembers({
|
||||
conversationId: conversationId.value,
|
||||
memberUserIds: checkedMemberIds.value,
|
||||
});
|
||||
createMessage.success('添加成功');
|
||||
emit('success');
|
||||
closeModal();
|
||||
} finally {
|
||||
setModalProps({ confirmLoading: false });
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.member-search {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.member-list {
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
border: 1px solid #f0f0f0;
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.member-checkbox-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.member-row {
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
.member-info {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
<!--update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置-添加群成员弹窗----------->
|
||||
473
jeecgboot-vue3/src/views/system/im/ImGroupSettingDrawer.vue
Normal file
473
jeecgboot-vue3/src/views/system/im/ImGroupSettingDrawer.vue
Normal file
@@ -0,0 +1,473 @@
|
||||
<!--update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置抽屉----------->
|
||||
<template>
|
||||
<BasicDrawer v-bind="$attrs" title="群设置" :width="360" @register="registerDrawer">
|
||||
<a-spin :spinning="loading">
|
||||
<div class="group-setting">
|
||||
<!-- 群成员宫格 -->
|
||||
<div class="section">
|
||||
<div class="section-title">
|
||||
群成员
|
||||
<span class="member-count">({{ detail.memberCount || 0 }})</span>
|
||||
</div>
|
||||
<div class="member-grid">
|
||||
<div v-for="m in detail.members" :key="m.userId" class="member-cell">
|
||||
<div class="member-avatar-wrap">
|
||||
<a-avatar :size="44" :src="getAvatarUrl(m.avatar)">
|
||||
{{ (m.realname || m.username || '?').slice(0, 1) }}
|
||||
</a-avatar>
|
||||
<span v-if="m.owner" class="owner-tag">群主</span>
|
||||
<span
|
||||
v-if="removeMode && !m.owner"
|
||||
class="remove-badge"
|
||||
@click="handleRemoveMember(m)"
|
||||
>
|
||||
<Icon icon="ant-design:minus-outlined" />
|
||||
</span>
|
||||
</div>
|
||||
<span class="member-name">{{ m.realname || m.username }}</span>
|
||||
</div>
|
||||
|
||||
<!-- 添加成员(所有成员可见) -->
|
||||
<div class="member-cell">
|
||||
<div class="member-action-btn" @click="handleOpenAddMember">
|
||||
<Icon icon="ant-design:plus-outlined" />
|
||||
</div>
|
||||
<span class="member-name">添加</span>
|
||||
</div>
|
||||
|
||||
<!-- 移除成员(仅群主可见) -->
|
||||
<div v-if="detail.owner" class="member-cell">
|
||||
<div :class="['member-action-btn', { active: removeMode }]" @click="toggleRemoveMode">
|
||||
<Icon icon="ant-design:minus-outlined" />
|
||||
</div>
|
||||
<span class="member-name">{{ removeMode ? '完成' : '移除' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 群名称 -->
|
||||
<div class="section">
|
||||
<div class="setting-row" :class="{ 'is-clickable': detail.owner }" @click="handleEditName">
|
||||
<span class="row-label">群聊名称</span>
|
||||
<span class="row-value">
|
||||
{{ detail.groupName || '未命名群聊' }}
|
||||
<Icon v-if="detail.owner" icon="ant-design:right-outlined" class="row-arrow" />
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 群主管理 -->
|
||||
<div v-if="detail.owner" class="section">
|
||||
<div class="setting-row is-clickable" @click="handleOpenTransfer">
|
||||
<span class="row-label">转让群主</span>
|
||||
<Icon icon="ant-design:right-outlined" class="row-arrow" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部操作 -->
|
||||
<div class="footer-actions">
|
||||
<a-button v-if="detail.owner" danger block @click="handleDismiss">解散群聊</a-button>
|
||||
<a-button v-else danger block @click="handleQuit">退出群聊</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</a-spin>
|
||||
|
||||
<!-- 添加成员弹窗 -->
|
||||
<ImGroupAddMemberModal @register="registerAddMemberModal" @success="onMembersChanged" />
|
||||
|
||||
<!-- 修改群名称弹窗 -->
|
||||
<a-modal v-model:open="nameModalVisible" title="修改群名称" :confirm-loading="nameSaving" @ok="submitRename">
|
||||
<a-input v-model:value="editingName" placeholder="请输入群名称" :maxlength="30" show-count />
|
||||
</a-modal>
|
||||
|
||||
<!-- 转让群主弹窗 -->
|
||||
<a-modal v-model:open="transferModalVisible" title="转让群主" :confirm-loading="transferSaving" @ok="submitTransfer">
|
||||
<div class="transfer-tip">选择一名群成员作为新群主,转让后你将不再是群主。</div>
|
||||
<a-radio-group v-model:value="transferTargetId" class="transfer-list">
|
||||
<a-radio v-for="m in transferCandidates" :key="m.userId" :value="m.userId" class="transfer-item">
|
||||
<a-avatar :size="28" :src="getAvatarUrl(m.avatar)">
|
||||
{{ (m.realname || m.username || '?').slice(0, 1) }}
|
||||
</a-avatar>
|
||||
<span class="transfer-name">{{ m.realname || m.username }}</span>
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
<a-empty v-if="!transferCandidates.length" description="暂无其他群成员" />
|
||||
</a-modal>
|
||||
</BasicDrawer>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
|
||||
import { useModal } from '/@/components/Modal';
|
||||
import { Icon } from '/@/components/Icon';
|
||||
import { useMessage } from '/@/hooks/web/useMessage';
|
||||
import { getFileAccessHttpUrl } from '/@/utils/common/compUtils';
|
||||
import {
|
||||
fetchGroupDetail,
|
||||
removeGroupMember,
|
||||
renameGroup,
|
||||
transferGroupOwner,
|
||||
quitGroup,
|
||||
dismissGroup,
|
||||
type ImGroupDetail,
|
||||
type ImGroupMember,
|
||||
} from './im.api';
|
||||
import ImGroupAddMemberModal from './ImGroupAddMemberModal.vue';
|
||||
|
||||
defineOptions({ name: 'ImGroupSettingDrawer' });
|
||||
|
||||
const emit = defineEmits<{
|
||||
/** 群信息变更(改名/加人/踢人/转让),父组件据此刷新会话 */
|
||||
(e: 'changed', payload: { conversationId: string; groupName?: string; memberCount?: number }): void;
|
||||
/** 退出或解散群聊,父组件据此移除会话并清理当前会话 */
|
||||
(e: 'exited', conversationId: string): void;
|
||||
}>();
|
||||
|
||||
const { createMessage, createConfirm } = useMessage();
|
||||
|
||||
const loading = ref(false);
|
||||
const conversationId = ref('');
|
||||
const detail = reactive<ImGroupDetail>({
|
||||
conversationId: '',
|
||||
groupName: '',
|
||||
ownerId: '',
|
||||
memberCount: 0,
|
||||
owner: false,
|
||||
members: [],
|
||||
});
|
||||
|
||||
const removeMode = ref(false);
|
||||
|
||||
// 改名
|
||||
const nameModalVisible = ref(false);
|
||||
const editingName = ref('');
|
||||
const nameSaving = ref(false);
|
||||
|
||||
// 转让群主
|
||||
const transferModalVisible = ref(false);
|
||||
const transferTargetId = ref('');
|
||||
const transferSaving = ref(false);
|
||||
const transferCandidates = computed(() => detail.members.filter((m) => !m.owner));
|
||||
|
||||
const [registerAddMemberModal, { openModal: openAddMemberModal }] = useModal();
|
||||
|
||||
const [registerDrawer, { setDrawerProps }] = useDrawerInner(async (data?: { conversationId?: string }) => {
|
||||
removeMode.value = false;
|
||||
conversationId.value = data?.conversationId || '';
|
||||
if (!conversationId.value) {
|
||||
return;
|
||||
}
|
||||
setDrawerProps({ loading: false });
|
||||
await loadDetail();
|
||||
});
|
||||
|
||||
function getAvatarUrl(avatar?: string) {
|
||||
return avatar ? getFileAccessHttpUrl(avatar) : '';
|
||||
}
|
||||
|
||||
function applyDetail(data: ImGroupDetail) {
|
||||
detail.conversationId = data.conversationId;
|
||||
detail.groupName = data.groupName;
|
||||
detail.ownerId = data.ownerId;
|
||||
detail.memberCount = data.memberCount;
|
||||
detail.owner = data.owner;
|
||||
detail.members = data.members || [];
|
||||
}
|
||||
|
||||
async function loadDetail() {
|
||||
loading.value = true;
|
||||
try {
|
||||
const data = await fetchGroupDetail(conversationId.value);
|
||||
applyDetail(data);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function notifyChanged() {
|
||||
emit('changed', {
|
||||
conversationId: conversationId.value,
|
||||
groupName: detail.groupName,
|
||||
memberCount: detail.memberCount,
|
||||
});
|
||||
}
|
||||
|
||||
function handleOpenAddMember() {
|
||||
openAddMemberModal(true, {
|
||||
conversationId: conversationId.value,
|
||||
existMemberIds: detail.members.map((m) => m.userId),
|
||||
});
|
||||
}
|
||||
|
||||
async function onMembersChanged() {
|
||||
await loadDetail();
|
||||
notifyChanged();
|
||||
}
|
||||
|
||||
function toggleRemoveMode() {
|
||||
removeMode.value = !removeMode.value;
|
||||
}
|
||||
|
||||
function handleRemoveMember(member: ImGroupMember) {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '移除成员',
|
||||
content: `确定将「${member.realname || member.username}」移出群聊吗?`,
|
||||
onOk: async () => {
|
||||
await removeGroupMember(conversationId.value, member.userId);
|
||||
createMessage.success('已移除');
|
||||
await loadDetail();
|
||||
notifyChanged();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleEditName() {
|
||||
if (!detail.owner) {
|
||||
return;
|
||||
}
|
||||
editingName.value = detail.groupName || '';
|
||||
nameModalVisible.value = true;
|
||||
}
|
||||
|
||||
async function submitRename() {
|
||||
const name = editingName.value.trim();
|
||||
if (!name) {
|
||||
createMessage.warning('请输入群名称');
|
||||
return;
|
||||
}
|
||||
nameSaving.value = true;
|
||||
try {
|
||||
await renameGroup(conversationId.value, name);
|
||||
detail.groupName = name;
|
||||
nameModalVisible.value = false;
|
||||
createMessage.success('修改成功');
|
||||
notifyChanged();
|
||||
} finally {
|
||||
nameSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleOpenTransfer() {
|
||||
transferTargetId.value = '';
|
||||
transferModalVisible.value = true;
|
||||
}
|
||||
|
||||
async function submitTransfer() {
|
||||
if (!transferTargetId.value) {
|
||||
createMessage.warning('请选择新群主');
|
||||
return;
|
||||
}
|
||||
transferSaving.value = true;
|
||||
try {
|
||||
await transferGroupOwner(conversationId.value, transferTargetId.value);
|
||||
transferModalVisible.value = false;
|
||||
createMessage.success('转让成功');
|
||||
await loadDetail();
|
||||
notifyChanged();
|
||||
} finally {
|
||||
transferSaving.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function handleQuit() {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '退出群聊',
|
||||
content: '退出后将不再接收该群消息,确定退出吗?',
|
||||
onOk: async () => {
|
||||
await quitGroup(conversationId.value);
|
||||
createMessage.success('已退出群聊');
|
||||
emit('exited', conversationId.value);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function handleDismiss() {
|
||||
createConfirm({
|
||||
iconType: 'warning',
|
||||
title: '解散群聊',
|
||||
content: '解散后群聊将被删除且无法恢复,确定解散吗?',
|
||||
onOk: async () => {
|
||||
await dismissGroup(conversationId.value);
|
||||
createMessage.success('群聊已解散');
|
||||
emit('exited', conversationId.value);
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.group-setting {
|
||||
padding: 4px 4px 16px;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
margin-bottom: 12px;
|
||||
|
||||
.member-count {
|
||||
color: #aaa;
|
||||
}
|
||||
}
|
||||
|
||||
.member-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px 8px;
|
||||
}
|
||||
|
||||
.member-cell {
|
||||
width: 56px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.member-avatar-wrap {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.owner-tag {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: -4px;
|
||||
transform: translateX(-50%);
|
||||
font-size: 10px;
|
||||
line-height: 14px;
|
||||
padding: 0 4px;
|
||||
color: #fff;
|
||||
background: #fa8c16;
|
||||
border-radius: 7px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.remove-badge {
|
||||
position: absolute;
|
||||
top: -4px;
|
||||
right: -4px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.member-action-btn {
|
||||
width: 44px;
|
||||
height: 44px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed #d9d9d9;
|
||||
border-radius: 6px;
|
||||
color: #999;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
|
||||
&:hover {
|
||||
border-color: #1677ff;
|
||||
color: #1677ff;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-color: #ff4d4f;
|
||||
color: #ff4d4f;
|
||||
}
|
||||
}
|
||||
|
||||
.member-name {
|
||||
font-size: 12px;
|
||||
color: #595959;
|
||||
max-width: 56px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.setting-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
min-height: 28px;
|
||||
|
||||
&.is-clickable {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.row-label {
|
||||
font-size: 14px;
|
||||
color: #262626;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.row-value {
|
||||
font-size: 13px;
|
||||
color: #888;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.row-arrow {
|
||||
color: #c0c0c0;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.footer-actions {
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.transfer-tip {
|
||||
color: #888;
|
||||
font-size: 13px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.transfer-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
max-height: 320px;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.transfer-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
:deep(.ant-radio + span) {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.transfer-name {
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
<!--update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置抽屉----------->
|
||||
@@ -8,6 +8,15 @@ enum Api {
|
||||
read = '/sys/im/chat/read',
|
||||
groups = '/sys/im/chat/groups',
|
||||
groupCreate = '/sys/im/chat/group/create',
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置接口-----------
|
||||
groupDetail = '/sys/im/chat/group/detail',
|
||||
groupAddMembers = '/sys/im/chat/group/addMembers',
|
||||
groupRemoveMember = '/sys/im/chat/group/removeMember',
|
||||
groupRename = '/sys/im/chat/group/rename',
|
||||
groupTransfer = '/sys/im/chat/group/transfer',
|
||||
groupQuit = '/sys/im/chat/group/quit',
|
||||
groupDismiss = '/sys/im/chat/group/dismiss',
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置接口-----------
|
||||
}
|
||||
|
||||
export interface ImGroupConversation {
|
||||
@@ -57,3 +66,51 @@ export const fetchGroups = () => defHttp.get<ImGroupConversation[]>({ url: Api.g
|
||||
|
||||
export const createGroupConversation = (data: { groupName: string; memberUserIds: string[] }) =>
|
||||
defHttp.post<ImGroupConversation>({ url: Api.groupCreate, data });
|
||||
|
||||
//update-begin---author:cursor ---date:20260529 for:【IM聊天-OA】群设置接口-----------
|
||||
export interface ImGroupMember {
|
||||
userId: string;
|
||||
realname?: string;
|
||||
username?: string;
|
||||
avatar?: string;
|
||||
owner?: boolean;
|
||||
}
|
||||
|
||||
export interface ImGroupDetail {
|
||||
conversationId: string;
|
||||
groupName?: string;
|
||||
ownerId?: string;
|
||||
memberCount?: number;
|
||||
/** 当前登录用户是否群主 */
|
||||
owner?: boolean;
|
||||
members: ImGroupMember[];
|
||||
}
|
||||
|
||||
/** 群聊详情(含成员列表) */
|
||||
export const fetchGroupDetail = (conversationId: string) =>
|
||||
defHttp.get<ImGroupDetail>({ url: Api.groupDetail, params: { conversationId } });
|
||||
|
||||
/** 添加群成员 */
|
||||
export const addGroupMembers = (data: { conversationId: string; memberUserIds: string[] }) =>
|
||||
defHttp.post<ImGroupConversation>({ url: Api.groupAddMembers, data });
|
||||
|
||||
/** 移除群成员(仅群主) */
|
||||
export const removeGroupMember = (conversationId: string, memberUserId: string) =>
|
||||
defHttp.post({ url: Api.groupRemoveMember, params: { conversationId, memberUserId } }, { joinParamsToUrl: true });
|
||||
|
||||
/** 修改群名称(仅群主) */
|
||||
export const renameGroup = (conversationId: string, groupName: string) =>
|
||||
defHttp.post<ImGroupConversation>({ url: Api.groupRename, params: { conversationId, groupName } }, { joinParamsToUrl: true });
|
||||
|
||||
/** 转让群主(仅群主) */
|
||||
export const transferGroupOwner = (conversationId: string, newOwnerId: string) =>
|
||||
defHttp.post({ url: Api.groupTransfer, params: { conversationId, newOwnerId } }, { joinParamsToUrl: true });
|
||||
|
||||
/** 退出群聊(非群主成员) */
|
||||
export const quitGroup = (conversationId: string) =>
|
||||
defHttp.post({ url: Api.groupQuit, params: { conversationId } }, { joinParamsToUrl: true });
|
||||
|
||||
/** 解散群聊(仅群主) */
|
||||
export const dismissGroup = (conversationId: string) =>
|
||||
defHttp.post({ url: Api.groupDismiss, params: { conversationId } }, { joinParamsToUrl: true });
|
||||
//update-end---author:cursor ---date:20260529 for:【IM聊天-OA】群设置接口-----------
|
||||
|
||||
Reference in New Issue
Block a user