Browse Source

ui美化

master
xsx 2 years ago
parent
commit
ee570cdbd5
  1. 19
      im-client/src/main/java/com/bx/imclient/IMClient.java
  2. 39
      im-client/src/main/java/com/bx/imclient/sender/IMSender.java
  3. 14
      im-platform/src/main/java/com/bx/implatform/controller/UserController.java
  4. 6
      im-platform/src/main/java/com/bx/implatform/service/IUserService.java
  5. 14
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  6. 1
      im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java
  7. 48
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  8. 3
      im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java
  9. 24
      im-platform/src/main/java/com/bx/implatform/vo/OnlineTerminalVO.java
  10. 4
      im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java
  11. 4
      im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java
  12. 8
      im-ui/src/api/enums.js
  13. BIN
      im-ui/src/assets/default_head.png
  14. 2
      im-ui/src/components/chat/ChatItem.vue
  15. 2
      im-ui/src/components/chat/ChatMessageItem.vue
  16. 63
      im-ui/src/components/common/HeadImage.vue
  17. 10
      im-ui/src/components/common/UserInfo.vue
  18. 5
      im-ui/src/components/friend/AddFriend.vue
  19. 19
      im-ui/src/components/friend/FriendItem.vue
  20. 2
      im-ui/src/components/group/GroupItem.vue
  21. 4
      im-ui/src/components/group/GroupMember.vue
  22. 26
      im-ui/src/store/friendStore.js
  23. 4
      im-ui/src/view/Friend.vue
  24. 11
      im-ui/src/view/Group.vue
  25. 10
      im-ui/src/view/Home.vue
  26. 2
      im-ui/src/view/Login.vue
  27. 8
      im-uniapp/common/enums.js
  28. 11
      im-uniapp/components/chat-item/chat-item.vue
  29. 65
      im-uniapp/components/chat-message-item/chat-message-item.vue
  30. 41
      im-uniapp/components/friend-item/friend-item.vue
  31. 20
      im-uniapp/components/group-item/group-item.vue
  32. 104
      im-uniapp/components/head-image/head-image.vue
  33. 28
      im-uniapp/pages/common/user-info.vue
  34. 32
      im-uniapp/pages/friend/friend-add.vue
  35. 4
      im-uniapp/pages/group/group-edit.vue
  36. 28
      im-uniapp/pages/group/group-info.vue
  37. 37
      im-uniapp/pages/group/group-invite.vue
  38. 43
      im-uniapp/pages/group/group-member.vue
  39. 34
      im-uniapp/store/friendStore.js

19
im-client/src/main/java/com/bx/imclient/IMClient.java

@ -1,14 +1,14 @@
package com.bx.imclient;
import com.bx.imclient.sender.IMSender;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMGroupMessage;
import com.bx.imcommon.model.IMPrivateMessage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@Configuration
public class IMClient {
@ -31,8 +31,19 @@ public class IMClient {
* @param userIds 用户id列表
* @return 在线的用户列表
*/
public List<Long> isOnline(List<Long> userIds){
return imSender.isOnline(userIds);
public List<Long> getOnlineUser(List<Long> userIds){
return imSender.getOnlineUser(userIds);
}
/**
* 判断多个用户是否在线
*
* @param userIds 用户id列表
* @return 在线的用户终端
*/
public Map<Long,List<IMTerminalType>> getOnlineTerminal(List<Long> userIds){
return imSender.getOnlineTerminal(userIds);
}
/**

39
im-client/src/main/java/com/bx/imclient/sender/IMSender.java

@ -14,6 +14,7 @@ import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
@Service
@ -90,12 +91,12 @@ public class IMSender {
List<Object> serverIds = redisTemplate.opsForValue().multiGet(sendMap.keySet());
// 格式:map<服务器id,list<接收方>>
Map<Integer, List<IMUserInfo>> serverMap = new HashMap<>();
List<IMUserInfo> offLineUsers = Collections.synchronizedList(new LinkedList<>());
List<IMUserInfo> offLineUsers = new LinkedList<>();
int idx = 0;
for (Map.Entry<String,IMUserInfo> entry : sendMap.entrySet()) {
Integer serverId = (Integer)serverIds.get(idx++);
if (serverId != null) {
List<IMUserInfo> list = serverMap.computeIfAbsent(serverId, o -> Collections.synchronizedList(new LinkedList<>()));
List<IMUserInfo> list = serverMap.computeIfAbsent(serverId, o -> new LinkedList<>());
list.add(entry.getValue());
} else {
// 加入离线列表
@ -150,34 +151,40 @@ public class IMSender {
}
}
public Boolean isOnline(Long userId) {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*");
return !redisTemplate.keys(key).isEmpty();
}
public List<Long> isOnline(List<Long> userIds){
public Map<Long,List<IMTerminalType>> getOnlineTerminal(List<Long> userIds){
if(CollectionUtil.isEmpty(userIds)){
return Collections.emptyList();
return Collections.EMPTY_MAP;
}
// 把所有用户的key都存起来
Map<String,Long> keyMap = new HashMap<>();
Map<String,IMUserInfo> userMap = new HashMap<>();
for(Long id:userIds){
for (Integer terminal : IMTerminalType.codes()) {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, id.toString(), terminal.toString());
keyMap.put(key,id);
userMap.put(key,new IMUserInfo(id,terminal));
}
}
// 批量拉取
List<Object> serverIds = redisTemplate.opsForValue().multiGet(keyMap.keySet());
List<Object> serverIds = redisTemplate.opsForValue().multiGet(userMap.keySet());
int idx = 0;
List<Long> onlineIds = new LinkedList<>();
for (Map.Entry<String,Long> entry : keyMap.entrySet()) {
Map<Long,List<IMTerminalType>> onlineMap = new HashMap<>();
for (Map.Entry<String,IMUserInfo> entry : userMap.entrySet()) {
// serverid有值表示用户在线
if(serverIds.get(idx++) != null){
onlineIds.add(entry.getValue());
IMUserInfo userInfo = entry.getValue();
List<IMTerminalType> terminals = onlineMap.computeIfAbsent(userInfo.getId(), o -> new LinkedList<>());
terminals.add(IMTerminalType.fromCode(userInfo.getTerminal()));
}
}
// 去重并返回
return onlineIds.stream().distinct().collect(Collectors.toList());
return onlineMap;
}
public Boolean isOnline(Long userId) {
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID, userId.toString(), "*");
return !redisTemplate.keys(key).isEmpty();
}
public List<Long> getOnlineUser(List<Long> userIds){
return new LinkedList<>(getOnlineTerminal(userIds).keySet());
}
}

14
im-platform/src/main/java/com/bx/implatform/controller/UserController.java

@ -7,6 +7,7 @@ import com.bx.implatform.service.IUserService;
import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.OnlineTerminalVO;
import com.bx.implatform.vo.UserVO;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
@ -34,6 +35,13 @@ public class UserController {
return ResultUtils.success(onlineIds);
}
@GetMapping("/terminal/online")
@ApiOperation(value = "判断用户哪个终端在线",notes="返回在线的用户id的终端集合")
public Result<List<OnlineTerminalVO>> getOnlineTerminal(@NotEmpty @RequestParam("userIds") String userIds){
return ResultUtils.success(userService.getOnlineTerminals(userIds));
}
@GetMapping("/self")
@ApiOperation(value = "获取当前用户信息",notes="获取当前用户信息")
public Result<UserVO> findSelfInfo(){
@ -46,10 +54,8 @@ public class UserController {
@GetMapping("/find/{id}")
@ApiOperation(value = "查找用户",notes="根据id查找用户")
public Result findByIde(@NotEmpty @PathVariable("id") long id){
User user = userService.getById(id);
UserVO userVO = BeanUtils.copyProperties(user,UserVO.class);
return ResultUtils.success(userVO);
public Result findById(@NotEmpty @PathVariable("id") Long id){
return ResultUtils.success(userService.findUserById(id));
}
@PutMapping("/update")

6
im-platform/src/main/java/com/bx/implatform/service/IUserService.java

@ -6,6 +6,7 @@ import com.bx.implatform.entity.User;
import com.bx.implatform.dto.LoginDTO;
import com.bx.implatform.dto.RegisterDTO;
import com.bx.implatform.vo.LoginVO;
import com.bx.implatform.vo.OnlineTerminalVO;
import com.bx.implatform.vo.UserVO;
import java.util.List;
@ -25,8 +26,13 @@ public interface IUserService extends IService<User> {
void update(UserVO vo);
UserVO findUserById(Long id);
List<UserVO> findUserByName(String name);
List<Long> checkOnline(String userIds);
List<OnlineTerminalVO> getOnlineTerminals(String userIds);
}

14
im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java

@ -3,6 +3,7 @@ package com.bx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.implatform.contant.Constant;
import com.bx.implatform.contant.RedisKey;
import com.bx.implatform.entity.Friend;
@ -52,6 +53,9 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
@Autowired
private IFriendService friendsService;
@Autowired
private IMClient imClient;
/**
* 创建新群聊
*
@ -292,7 +296,15 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
@Override
public List<GroupMemberVO> findGroupMembers(Long groupId) {
List<GroupMember> members = groupMemberService.findByGroupId(groupId);
return members.stream().map(m->BeanUtils.copyProperties(m,GroupMemberVO.class)).collect(Collectors.toList());
List<Long> userIds = members.stream().map(GroupMember::getUserId).collect(Collectors.toList());
List<Long> onlineUserIds = imClient.getOnlineUser(userIds);
return members.stream().map(m->{
GroupMemberVO vo = BeanUtils.copyProperties(m,GroupMemberVO.class);
vo.setOnline(onlineUserIds.contains(m.getUserId()));
return vo;
}).sorted((m1,m2)->{
return m2.getOnline().compareTo(m1.getOnline());
}).collect(Collectors.toList());
}
}

1
im-platform/src/main/java/com/bx/implatform/service/impl/PrivateMessageServiceImpl.java

@ -6,7 +6,6 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.contant.IMConstant;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.model.IMPrivateMessage;
import com.bx.imcommon.model.IMUserInfo;
import com.bx.implatform.entity.Friend;

48
im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java

@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.bx.imclient.IMClient;
import com.bx.imcommon.enums.IMTerminalType;
import com.bx.imcommon.util.JwtUtil;
import com.bx.implatform.config.JwtProperties;
import com.bx.implatform.dto.LoginDTO;
@ -24,6 +25,7 @@ import com.bx.implatform.session.SessionContext;
import com.bx.implatform.session.UserSession;
import com.bx.implatform.util.BeanUtils;
import com.bx.implatform.vo.LoginVO;
import com.bx.implatform.vo.OnlineTerminalVO;
import com.bx.implatform.vo.UserVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@ -33,7 +35,9 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@ -95,7 +99,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
/**
* 用refreshToken换取新 token
*
* @param refreshToken
* @param refreshToken 刷新token
* @return 登录token
*/
@Override
@ -150,7 +154,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
* 根据用户名查询用户
*
* @param username 用户名
* @return
* @return 用户信息
*/
@Override
public User findUserByUserName(String username) {
@ -205,12 +209,25 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
log.info("用户信息更新,用户:{}}", user);
}
/**
* 根据用户昵id查询用户以及在线状态
*
* @param id 用户id
* @return 用户信息
*/
@Override
public UserVO findUserById(Long id) {
User user = this.getById(id);
UserVO vo = BeanUtils.copyProperties(user,UserVO.class);
vo.setOnline(imClient.isOnline(id));
return vo;
}
/**
* 根据用户昵称查询用户最多返回20条数据
*
* @param name 用户名或昵称
* @return
* @return 用户列表
*/
@Override
public List<UserVO> findUserByName(String name) {
@ -221,7 +238,7 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
.last("limit 20");
List<User> users = this.list(queryWrapper);
List<Long> userIds = users.stream().map(User::getId).collect(Collectors.toList());
List<Long> onlineUserIds = imClient.isOnline(userIds);
List<Long> onlineUserIds = imClient.getOnlineUser(userIds);
return users.stream().map(u-> {
UserVO vo = BeanUtils.copyProperties(u,UserVO.class);
vo.setOnline(onlineUserIds.contains(u.getId()));
@ -239,7 +256,28 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IU
public List<Long> checkOnline(String userIds) {
List<Long> userIdList = Arrays.stream(userIds.split(","))
.map(Long::parseLong).collect(Collectors.toList());
return imClient.isOnline(userIdList);
return imClient.getOnlineUser(userIdList);
}
/**
* 获取用户在线的终端类型
*
* @param userIds 用户id多个用,分割
* @return 在线用户终端
*/
@Override
public List<OnlineTerminalVO> getOnlineTerminals(String userIds) {
List<Long> userIdList = Arrays.stream(userIds.split(","))
.map(Long::parseLong).collect(Collectors.toList());
// 查询在线的终端
Map<Long,List<IMTerminalType>> terminalMap = imClient.getOnlineTerminal(userIdList);
// 组装vo
List<OnlineTerminalVO> vos = new LinkedList<>();
terminalMap.forEach((userId,types)->{
List<Integer> terminals = types.stream().map(IMTerminalType::code).collect(Collectors.toList());
vos.add(new OnlineTerminalVO(userId,terminals));
});
return vos;
}
}

3
im-platform/src/main/java/com/bx/implatform/vo/GroupMemberVO.java

@ -21,6 +21,9 @@ public class GroupMemberVO {
@ApiModelProperty("是否已退出")
private Boolean quit;
@ApiModelProperty(value = "是否在线")
private Boolean online;
@ApiModelProperty("备注")
private String remark;

24
im-platform/src/main/java/com/bx/implatform/vo/OnlineTerminalVO.java

@ -0,0 +1,24 @@
package com.bx.implatform.vo;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import java.util.List;
/**
* @author: 谢绍许
* @date: 2023-10-28 21:17:59
* @version: 1.0
*/
@Data
@AllArgsConstructor
public class OnlineTerminalVO {
@ApiModelProperty(value = "用户id")
private Long userId;
@ApiModelProperty(value = "在线终端类型")
private List<Integer> terminals;
}

4
im-server/src/main/java/com/bx/imserver/netty/IMChannelHandler.java

@ -91,8 +91,8 @@ public class IMChannelHandler extends SimpleChannelInboundHandler<IMSendInfo> {
AttributeKey<Long> attr = AttributeKey.valueOf("USER_ID");
Long userId = ctx.channel().attr(attr).get();
AttributeKey<Integer> terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE);
Integer ternimal = ctx.channel().attr(terminalAttr).get();
log.info("心跳超时,即将断开连接,用户id:{},终端类型:{} ",userId,ternimal);
Integer terminal = ctx.channel().attr(terminalAttr).get();
log.info("心跳超时,即将断开连接,用户id:{},终端类型:{} ",userId,terminal);
ctx.channel().close();
}
} else {

4
im-server/src/main/java/com/bx/imserver/netty/processor/HeartbeatProcessor.java

@ -45,8 +45,8 @@ public class HeartbeatProcessor extends AbstractMessageProcessor<IMHeartbeatInfo
AttributeKey<Long> userIdAttr = AttributeKey.valueOf(ChannelAttrKey.USER_ID);
Long userId = ctx.channel().attr(userIdAttr).get();
AttributeKey<Integer> terminalAttr = AttributeKey.valueOf(ChannelAttrKey.TERMINAL_TYPE);
Integer ternimal = ctx.channel().attr(terminalAttr).get();
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(),ternimal.toString());
Integer terminal = ctx.channel().attr(terminalAttr).get();
String key = String.join(":", IMRedisKey.IM_USER_SERVER_ID,userId.toString(),terminal.toString());
redisTemplate.expire(key, IMConstant.ONLINE_TIMEOUT_SECOND, TimeUnit.SECONDS);
}
}

8
im-ui/src/api/enums.js

@ -22,7 +22,13 @@ const USER_STATE = {
BUSY: 2
}
const TERMINAL_TYPE = {
WEB: 0,
APP: 1
}
export {
MESSAGE_TYPE,
USER_STATE
USER_STATE,
TERMINAL_TYPE
}

BIN
im-ui/src/assets/default_head.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

2
im-ui/src/components/chat/ChatItem.vue

@ -1,7 +1,7 @@
<template>
<div class="chat-item" :class="active ? 'active' : ''" @contextmenu.prevent="showRightMenu($event)">
<div class="chat-left">
<head-image :url="chat.headImage" :size="50" :id="chat.type=='PRIVATE'?chat.targetId:0"></head-image>
<head-image :url="chat.headImage" :name="chat.showName" :size="50" :id="chat.type=='PRIVATE'?chat.targetId:0"></head-image>
<div v-show="chat.unreadCount>0" class="unread-text">{{chat.unreadCount}}</div>
</div>
<div class="chat-right">

2
im-ui/src/components/chat/ChatMessageItem.vue

@ -5,7 +5,7 @@
<div class="chat-msg-normal" v-show="msgInfo.type>=0 && msgInfo.type<10" :class="{'chat-msg-mine':mine}">
<div class="head-image">
<head-image :size="40" :url="headImage" :id="msgInfo.sendId"></head-image>
<head-image :name="showName" :size="40" :url="headImage" :id="msgInfo.sendId"></head-image>
</div>
<div class="chat-msg-content">
<div v-show="mode==1 && msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top">

63
im-ui/src/components/common/HeadImage.vue

@ -1,6 +1,9 @@
<template>
<div class="head-image" @click="showUserInfo($event)">
<img :src="url" :style="{width: size+'px',height: size+'px',cursor: 'pointer'}" />
<img class="avatar-image" v-show="url" :src="url" :style="avatarImageStyle" />
<div class="avatar-text" v-show="!url" :style="avatarTextStyle">
{{name.substring(0,1).toUpperCase()}}</div>
<div v-show="online" class="online" title="用户当前在线"></div>
<slot></slot>
</div>
</template>
@ -10,7 +13,11 @@
export default {
name: "headImage",
data() {
return {}
return {
colors:["#7dd24b","#c7515a","#db68ef","#15d29b","#85029b",
"#c9b455","#fb2609","#bda818","#af0831","#326eb6"]
}
},
props: {
id:{
@ -22,6 +29,14 @@
},
url: {
type: String
},
name:{
type: String,
default: "X"
},
online:{
type: Boolean,
default:false
}
},
methods:{
@ -36,6 +51,22 @@
})
}
}
},
computed:{
avatarImageStyle(){
return `width:${this.size}px; height:${this.size}px;`
},
avatarTextStyle(){
return `width: ${this.size}px;height:${this.size}px;
color:${this.textColor};font-size:${this.size*0.6}px;`
},
textColor(){
let hash = 0;
for (var i = 0; i< this.name.length; i++) {
hash += this.name.charCodeAt(i);
}
return this.colors[hash%this.colors.length];
}
}
}
</script>
@ -43,19 +74,31 @@
<style scoped lang="scss">
.head-image {
position: relative;
img {
cursor: pointer;
.avatar-image {
position: relative;
overflow: hidden;
border-radius: 10%;
}
img:before {
content: '';
display: block;
width: 100%;
height: 100%;
background: url('../../assets/default_head.png') no-repeat 0 0;
background-size: 100%;
.avatar-text{
background-color: #f2f2f2; /* 默认背景色 */
border-radius: 50%; /* 圆角效果 */
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ccc;
}
.online{
position: absolute;
right: -5px;
bottom: 0;
width: 12px;
height: 12px;
background: limegreen;
border-radius: 50%;
border: 3px solid white;
}
}
</style>

10
im-ui/src/components/common/UserInfo.vue

@ -3,7 +3,8 @@
<div class="user-info" :style="{left: pos.x+'px',top: pos.y+'px'}" @click.stop>
<div class="user-info-box">
<div class="avatar">
<head-image :url="user.headImageThumb" :size="60" @click.native="showFullImage()"> </head-image>
<head-image :name="user.nickName" :url="user.headImageThumb" :size="60" :online="user.online"
@click.native="showFullImage()"> </head-image>
</div>
<div>
<el-descriptions :column="1" :title="user.userName" class="user-info-items">
@ -79,9 +80,9 @@
this.$store.commit("addFriend", friend);
})
},
showFullImage(){
if(this.user.headImage){
this.$store.commit("showFullImageBox",this.user.headImage);
showFullImage() {
if (this.user.headImage) {
this.$store.commit("showFullImageBox", this.user.headImage);
}
}
},
@ -118,6 +119,7 @@
margin-left: 10px;
white-space: nowrap;
overflow: hidden;
.el-descriptions__header {
margin-bottom: 5px;
}

5
im-ui/src/components/friend/AddFriend.vue

@ -7,7 +7,10 @@
<div v-for="(user) in users" :key="user.id" v-show="user.id != $store.state.userStore.userInfo.id">
<div class="item">
<div class="avatar">
<head-image :url="user.headImage"></head-image>
<head-image :name="user.nickName"
:url="user.headImage"
:online="user.online"
></head-image>
</div>
<div class="add-friend-text">
<div class="text-user-name">

19
im-ui/src/components/friend/FriendItem.vue

@ -1,11 +1,17 @@
<template>
<div class="friend-item" :class="active ? 'active' : ''" @contextmenu.prevent="showRightMenu($event)">
<div class="friend-avatar">
<head-image :url="friend.headImage"> </head-image>
<head-image :name="friend.nickName"
:url="friend.headImage"
:online="friend.online">
</head-image>
</div>
<div class="friend-info">
<div class="friend-name">{{ friend.nickName}}</div>
<div class="friend-online" :class="friend.online ? 'online':''">{{ friend.online?"[在线]":"[离线]"}}</div>
<div class="friend-online online">
<span v-show="friend.onlineWeb" class="el-icon-s-platform" title="电脑端在线"></span>
<span v-show="friend.onlineApp" class="el-icon-mobile-phone" title="移动端在线"></span>
</div>
</div>
<right-menu v-show="menu && rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items"
@close="rightMenu.show=false" @select="handleSelectMenu"></right-menu>
@ -121,11 +127,10 @@
}
.friend-online {
font-size: 12px;
&.online {
color: #5fb878;
}
padding-right: 15px;
font-size: 16px;
font-weight: 600;
color: #2f6dce;
}
}
}

2
im-ui/src/components/group/GroupItem.vue

@ -1,7 +1,7 @@
<template>
<div class="group-item" :class="active ? 'active' : ''">
<div class="group-avatar">
<head-image :url="group.headImage"> </head-image>
<head-image :name="group.remark" :url="group.headImage"> </head-image>
</div>
<div class="group-name">
<div>{{group.remark}}</div>

4
im-ui/src/components/group/GroupMember.vue

@ -1,6 +1,8 @@
<template>
<div class="group-member">
<head-image :url="member.headImage" :size="50" :id="member.userId">
<head-image :id="member.userId" :name="member.aliasName"
:url="member.headImage" :size="50"
:online="member.online" >
<div v-if="showDel" @click.stop="handleDelete()" class="btn-kick el-icon-error"></div>
</head-image>
<div class="member-name">{{member.aliasName}}</div>

26
im-ui/src/store/friendStore.js

@ -1,4 +1,5 @@
import httpRequest from '../api/httpRequest.js'
import {TERMINAL_TYPE} from "../api/enums.js"
export default {
@ -51,11 +52,11 @@ export default {
}
state.friends.forEach((f)=>{userIds.push(f.id)});
httpRequest({
url: '/user/online',
url: '/user/terminal/online',
method: 'get',
params: {userIds: userIds.join(',')}
}).then((onlineIds) => {
this.commit("setOnlineStatus",onlineIds);
}).then((onlineTerminals) => {
this.commit("setOnlineStatus",onlineTerminals);
})
// 30s后重新拉取
@ -64,13 +65,22 @@ export default {
this.commit("refreshOnlineStatus");
},30000)
},
setOnlineStatus(state,onlineIds){
console.log("setOnlineStatus")
setOnlineStatus(state,onlineTerminals){
state.friends.forEach((f)=>{
let onlineFriend = onlineIds.find((id)=> f.id==id);
f.online = onlineFriend != undefined;
let userTerminal = onlineTerminals.find((o)=> f.id==o.userId);
if(userTerminal){
console.log(userTerminal)
f.online = true;
f.onlineTerminals = userTerminal.terminals;
f.onlineWeb = userTerminal.terminals.indexOf(TERMINAL_TYPE.WEB)>=0
f.onlineApp = userTerminal.terminals.indexOf(TERMINAL_TYPE.APP)>=0
}else{
f.online = false;
f.onlineTerminals = [];
f.onlineWeb = false;
f.onlineApp = false;
}
});
let activeFriend = state.friends[state.activeIndex];
state.friends.sort((f1,f2)=>{
if(f1.online&&!f2.online){

4
im-ui/src/view/Friend.vue

@ -27,7 +27,9 @@
</div>
<div v-show="userInfo.id">
<div class="user-detail">
<head-image class="detail-head-image" :size="200" :url="userInfo.headImage"
<head-image class="detail-head-image" :size="200"
:name="userInfo.nickName"
:url="userInfo.headImage"
@click.native="showFullImage()"></head-image>
<div class="info-item">
<el-descriptions title="好友信息" class="description" :column="1">

11
im-ui/src/view/Group.vue

@ -26,12 +26,16 @@
<div v-show="activeGroup.id">
<div class="r-group-info">
<div>
<file-upload class="avatar-uploader" :action="imageAction" :disabled="!isOwner"
<file-upload v-show="isOwner" class="avatar-uploader" :action="imageAction"
:showLoading="true" :maxSize="maxSize" @success="handleUploadSuccess"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg','image/webp']">
<img v-if="activeGroup.headImage" :src="activeGroup.headImage" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</file-upload>
<head-image v-show="!isOwner" class="avatar" :size="200"
:url="activeGroup.headImage"
:name="activeGroup.remark">
</head-image>
<el-button class="send-btn" @click="handleSendMessage()">发送消息</el-button>
</div>
<el-form class="r-group-form" label-width="130px" :model="activeGroup" :rules="rules"
@ -91,14 +95,15 @@
import FileUpload from '../components/common/FileUpload';
import GroupMember from '../components/group/GroupMember.vue';
import AddGroupMember from '../components/group/AddGroupMember.vue';
import HeadImage from '../components/common/HeadImage.vue';
export default {
name: "group",
components: {
GroupItem,
GroupMember,
FileUpload,
AddGroupMember
AddGroupMember,
HeadImage
},
data() {
return {

10
im-ui/src/view/Home.vue

@ -2,7 +2,9 @@
<el-container>
<el-aside width="80px" class="navi-bar">
<div class="user-head-image">
<head-image :url="$store.state.userStore.userInfo.headImageThumb" :size="60" @click.native="showSettingDialog=true">
<head-image :name="$store.state.userStore.userInfo.nickName"
:url="$store.state.userStore.userInfo.headImageThumb"
:size="60" @click.native="showSettingDialog=true">
</head-image>
</div>
@ -269,12 +271,6 @@
padding: 10px;
padding-top: 50px;
.user-head-image {
position: relative;
width: 50px;
height: 50px;
}
.el-menu {
border: none;
flex: 1;

2
im-ui/src/view/Login.vue

@ -44,7 +44,7 @@
};
return {
loginForm: {
terminal: 0,
terminal: this.$enums.TERMINAL_TYPE.WEB,
userName: '',
password: ''
},

8
im-uniapp/common/enums.js

@ -22,7 +22,13 @@ const USER_STATE = {
BUSY: 2
}
const TERMINAL_TYPE = {
WEB: 0,
APP: 1
}
export {
MESSAGE_TYPE,
USER_STATE
USER_STATE,
TERMINAL_TYPE
}

11
im-uniapp/components/chat-item/chat-item.vue

@ -1,8 +1,8 @@
<template>
<view class="chat-item" @click="showChatBox()">
<view class="left">
<image class="head-image" :src="chat.headImage" mode="aspectFill" lazy-load="true"></image>
<view v-show="chat.unreadCount>0" class="unread-text">{{chat.unreadCount}}</view>
<head-image :url="chat.headImage" :name="chat.showName" :size="100"></head-image>
<view v-if="chat.unreadCount>0" class="unread-text">{{chat.unreadCount}}</view>
</view>
<view class="chat-right">
<view class="chat-name">
@ -64,13 +64,6 @@
width: 100rpx;
height: 100rpx;
.head-image {
width: 100%;
height: 100%;
border-radius: 10%;
border: #eeeeee solid 1px;
}
.unread-text {
position: absolute;
background-color: red;

65
im-uniapp/components/chat-message-item/chat-message-item.vue

@ -1,17 +1,19 @@
<template>
<view class="chat-msg-item">
<view class="chat-msg-tip" v-show="msgInfo.type==$enums.MESSAGE_TYPE.RECALL">{{msgInfo.content}}</view>
<view class="chat-msg-tip" v-show="msgInfo.type==$enums.MESSAGE_TYPE.TIP_TIME">
<view class="chat-msg-tip" v-if="msgInfo.type==$enums.MESSAGE_TYPE.RECALL">{{msgInfo.content}}</view>
<view class="chat-msg-tip" v-if="msgInfo.type==$enums.MESSAGE_TYPE.TIP_TIME">
{{$date.toTimeText(msgInfo.sendTime)}}</view>
<view class="chat-msg-normal" v-show="msgInfo.type>=0 && msgInfo.type<10"
<view class="chat-msg-normal" v-if="msgInfo.type>=0 && msgInfo.type<10"
:class="{'chat-msg-mine':msgInfo.selfSend}">
<view class="avatar" @click="onShowUserInfo(msgInfo.sendId)">
<image class="head-image" :src="headImage" lazy-load="true"></image>
</view>
<head-image class="avatar" :id="msgInfo.sendId" :url="headImage"
:name="showName" :size="80"></head-image>
<view class="chat-msg-content" @longpress="onShowMenu($event)">
<view v-show="msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top">
<view v-if="msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top">
<text>{{showName}}</text>
</view>
@ -20,12 +22,12 @@
:nodes="$emo.transform(msgInfo.content)"></rich-text>
<view class="chat-msg-image" v-if="msgInfo.type==$enums.MESSAGE_TYPE.IMAGE">
<view class="img-load-box">
<image class="send-image" :src="JSON.parse(msgInfo.content).thumbUrl" lazy-load="true"
<image class="send-image" mode="widthFix" :src="JSON.parse(msgInfo.content).thumbUrl" lazy-load="true"
@click.stop="onShowFullImage()">
</image>
<loading v-show="loading"></loading>
<loading v-if="loading"></loading>
</view>
<text title="发送失败" v-show="loadFail" @click="onSendFail"
<text title="发送失败" v-if="loadFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></text>
</view>
@ -37,9 +39,9 @@
<view class="chat-file-size">{{fileSize}}</view>
</view>
<view class="chat-file-icon iconfont icon-file"></view>
<loading v-show="loading"></loading>
<loading v-if="loading"></loading>
</view>
<text title="发送失败" v-show="loadFail" @click="onSendFail"
<text title="发送失败" v-if="loadFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></text>
</view>
<!--
@ -52,7 +54,7 @@
</view>
</view>
<pop-menu v-show="menu.show" :menu-style="menu.style" :items="menuItems" @close="menu.show=false"
<pop-menu v-if="menu.show" :menu-style="menu.style" :items="menuItems" @close="menu.show=false"
@select="onSelectMenu"></pop-menu>
</view>
</template>
@ -132,13 +134,7 @@
uni.previewImage({
urls: [imageUrl]
})
},
onShowUserInfo(userId){
uni.navigateTo({
url: "/pages/common/user-info?id=" + userId
})
}
},
computed: {
loading() {
@ -201,24 +197,13 @@
position: relative;
font-size: 0;
margin-bottom: 15rpx;
padding-left: 100rpx;
padding-left: 110rpx;
min-height: 80rpx;
.avatar {
position: absolute;
display: flex;
justify-content: center;
align-items: center;
width: 70rpx;
height: 70rpx;
top: 0;
left: 0;
.head-image {
width: 100%;
height: 100%;
border-radius: 10%;
}
}
@ -243,7 +228,7 @@
line-height: 60rpx;
margin-top: 10rpx;
padding: 10rpx;
background-color: rgb(235,235,245);
background-color: #ebebf5;
border-radius: 10rpx;
color: #333;
font-size: 30rpx;
@ -260,7 +245,7 @@
width: 0;
height: 0;
border-style: solid dashed dashed;
border-color: rgb(235,235,245) transparent transparent;
border-color: #ebebf5 transparent transparent;
overflow: hidden;
border-width: 20rpx;
}
@ -279,10 +264,8 @@
.send-image {
min-width: 200rpx;
min-height: 150rpx;
max-width: 400rpx;
max-height: 300rpx;
border: #dddddd solid 1px;
box-shadow: 2px 2px 2px #c0c0c0;
max-width: 500rpx;
border: 8rpx solid #ebebf5;
cursor: pointer;
}
}
@ -360,7 +343,7 @@
&.chat-msg-mine {
text-align: right;
padding-left: 0;
padding-right: 100rpx;
padding-right: 110rpx;
.avatar {
left: auto;
@ -375,13 +358,13 @@
padding-right: 0;
.chat-msg-text {
margin-left: 10px;
background-color: rgb(88, 127, 240);
background-color: #587ff0;
color: #fff;
box-shadow: 2px 2px 1px #ccc;
box-shadow: 1px 1px 1px #ccc;
&:after {
left: auto;
right: -10px;
border-top-color: rgb(88, 127, 240);
border-top-color: #587ff0;
}
}

41
im-uniapp/components/friend-item/friend-item.vue

@ -1,13 +1,12 @@
<template>
<view class="friend-item" @click="showFriendInfo()">
<view class="friend-avatar">
<image class="head-image" :src="friend.headImage" lazy-load="true" mode="aspectFill"></image>
</view>
<head-image :name="friend.nickName" :online="friend.online" :url="friend.headImage"
:size="100"></head-image>
<view class="friend-info">
<view class="friend-name">{{ friend.nickName}}</view>
<view class="friend-online"
:class="friend.online ? 'online':''">
{{ friend.online?"[在线]":"[离线]"}}
<view class="friend-online">
<text v-show="friend.onlineWeb" title="网页端在线">网页端</text>
<text v-show="friend.onlineApp" title="移动端在线">移动端</text>
</view>
</view>
</view>
@ -19,10 +18,10 @@
data() {
return {}
},
methods:{
showFriendInfo(){
methods: {
showFriendInfo() {
uni.navigateTo({
url: "/pages/common/user-info?id="+this.friend.id
url: "/pages/common/user-info?id=" + this.friend.id
})
},
},
@ -45,25 +44,11 @@
padding-right: 10rpx;
background-color: white;
white-space: nowrap;
&:hover {
background-color: #eeeeee;
}
.friend-avatar {
display: flex;
justify-content: center;
align-items: center;
width: 100rpx;
height: 100rpx;
.head-image{
width: 100%;
height: 100%;
border-radius: 10%;
border: #eeeeee solid 1px;
}
}
.friend-info {
flex: 1;
display: flex;
@ -78,14 +63,6 @@
white-space: nowrap;
overflow: hidden;
}
.friend-online {
font-size: 28rpx;
&.online {
color: #5fb878;
}
}
}
}
</style>

20
im-uniapp/components/group-item/group-item.vue

@ -1,8 +1,7 @@
<template>
<view class="group-item" @click="showGroupInfo()">
<view class="group-avatar">
<image class="head-image" :src="group.headImage" lazy-load="true" mode="aspectFill"></image>
</view>
<head-image :name="group.remark"
:url="group.headImage" :size="100"></head-image>
<view class="group-name">
<view>{{ group.remark}}</view>
</view>
@ -45,21 +44,6 @@
background-color: #eeeeee;
}
.group-avatar {
display: flex;
justify-content: center;
align-items: center;
width: 100rpx;
height: 100rpx;
.head-image{
width: 100%;
height: 100%;
border-radius: 10%;
border: #eeeeee solid 1px;
}
}
.group-name {
font-size: 32rpx;
padding-left: 20rpx;

104
im-uniapp/components/head-image/head-image.vue

@ -0,0 +1,104 @@
<template>
<view class="head-image" @click="showUserInfo($event)">
<image class="avatar-image" v-if="url" :src="url"
:style="avatarImageStyle" lazy-load="true" mode="aspectFill"/>
<view class="avatar-text" v-if="!url" :style="avatarTextStyle">
{{name.substring(0,1).toUpperCase()}}
</view>
<view v-if="online" class="online" title="用户当前在线">
</view>
</view>
</template>
<script>
export default {
name: "head-image",
data() {
return {
colors: ["#7dd24b", "#c7515a", "#db68ef", "#15d29b", "#85029b",
"#c9b455", "#fb2609", "#bda818", "#af0831", "#326eb6"
]
}
},
props: {
id: {
type: Number
},
size: {
type: Number,
default: 50
},
url: {
type: String
},
name: {
type: String,
default: "?"
},
online: {
type: Boolean,
default: false
}
},
methods: {
showUserInfo(e) {
if (this.id && this.id > 0) {
uni.navigateTo({
url: "/pages/common/user-info?id="+this.id
})
}
}
},
computed: {
avatarImageStyle() {
return `width:${this.size}rpx; height:${this.size}rpx;`
},
avatarTextStyle() {
return `width: ${this.size}rpx;height:${this.size}rpx;
color:${this.textColor};font-size:${this.size*0.6}rpx;`
},
textColor() {
let hash = 0;
for (var i = 0; i < this.name.length; i++) {
hash += this.name.charCodeAt(i);
}
return this.colors[hash % this.colors.length];
}
}
}
</script>
<style scoped lang="scss">
.head-image {
position: relative;
cursor: pointer;
.avatar-image {
position: relative;
overflow: hidden;
border-radius: 10%;
}
.avatar-text {
background-color: #f2f2f2;
/* 默认背景色 */
border-radius: 50%;
/* 圆角效果 */
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ccc;
}
.online {
position: absolute;
right: -10%;
bottom: 0;
width: 24rpx;
height: 24rpx;
background: limegreen;
border-radius: 50%;
border: 6rpx solid white;
}
}
</style>

28
im-uniapp/pages/common/user-info.vue

@ -1,9 +1,9 @@
<template>
<view class="page user-info">
<view class="content">
<view class="avatar" @click="onShowFullImage()">
<image class="head-image" :src="userInfo.headImage" lazy-load="true" mode="aspectFill"></image>
</view>
<head-image :name="userInfo.nickName" :url="userInfo.headImage"
:size="160" @click="onShowFullImage()"></head-image>
<view class="info-item">
<view class="info-primary">
<text class="info-username">
@ -41,9 +41,11 @@
methods: {
onShowFullImage(){
let imageUrl = this.userInfo.headImage;
uni.previewImage({
urls: [imageUrl]
})
if(imageUrl){
uni.previewImage({
urls: [imageUrl]
})
}
},
onSendMessage() {
let chat = {
@ -152,20 +154,6 @@
justify-content: space-between;
padding: 20rpx;
.avatar {
display: flex;
justify-content: center;
align-items: center;
width: 160rpx;
height: 160rpx;
.head-image {
width: 100%;
height: 100%;
border-radius: 10%;
}
}
.info-item {
display: flex;
align-items: flex-start;

32
im-uniapp/pages/friend/friend-add.vue

@ -7,14 +7,17 @@
<view class="user-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="(user) in users" :key="user.id" v-show="user.id != $store.state.userStore.userInfo.id">
<uni-list-chat :avatar="user.headImage" :title="user.userName" :note="'昵称:'+user.nickName"
:clickable="true" @click="onShowUserInfo(user)">
<view class="chat-custom-right">
<view class="user-item" @click="onSwitchChecked(friend)">
<head-image :id="user.id" :name="user.nickName"
:online="user.online" :url="user.headImage"
:size="100"></head-image>
<view class="user-name">{{ user.nickName}}</view>
<view class="user-btns">
<button type="primary" v-show="!isFriend(user.id)" size="mini"
@click.stop="onAddFriend(user)">加为好友</button>
<button type="default" v-show="isFriend(user.id)" size="mini" disabled>已添加</button>
</view>
</uni-list-chat>
</view>
</view>
</scroll-view>
</view>
@ -87,6 +90,27 @@
position: relative;
flex: 1;
overflow: hidden;
.user-item {
height: 120rpx;
display: flex;
margin-bottom: 1rpx;
position: relative;
padding: 0 30rpx ;
align-items: center;
background-color: white;
white-space: nowrap;
.user-name {
flex:1;
padding-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
}
}
.scroll-bar {
height: 100%;
}

4
im-uniapp/pages/group/group-edit.vue

@ -3,9 +3,11 @@
<uni-forms ref="form" :modelValue="group" :rules="rules" validate-trigger="bind" label-position="top"
label-width="100%">
<uni-forms-item label="群聊头像:" name="headImage">
<image-upload :disabled="!isOwner" :onSuccess="onUnloadImageSuccess">
<image-upload v-show="isOwner" :onSuccess="onUnloadImageSuccess">
<image :src="group.headImage" class="head-image"></image>
</image-upload>
<head-image v-show="!isOwner" :name="group.remark"
:url="group.headImage" :size="200"></head-image>
</uni-forms-item>
<uni-forms-item label="群聊名称:" name="name" :required="true">
<uni-easyinput type="text" v-model="group.name" :disabled="!isOwner" placeholder="请输入群聊名称" />

28
im-uniapp/pages/group/group-info.vue

@ -3,10 +3,9 @@
<view class="group-members">
<view class="member-items">
<view v-for="(member,idx) in groupMembers" :key="idx">
<view class="member-item" v-if="idx<9" @click="onShowUserInfo(member.userId)">
<view class="member-avatar">
<image class="head-image" :src="member.headImage" mode="aspectFill"> </image>
</view>
<view class="member-item" v-if="idx<9">
<head-image :id="member.userId" :name="member.aliasName" :url="member.headImage"
:size="100" :online="member.online" ></head-image>
<view class="member-name">
<text>{{member.aliasName}}</text>
</view>
@ -69,11 +68,6 @@
url: `/pages/group/group-invite?id=${this.groupId}`
})
},
onShowUserInfo(userId) {
uni.navigateTo({
url: "/pages/common/user-info?id=" + userId
})
},
onShowMoreMmeber() {
uni.navigateTo({
url: `/pages/group/group-member?id=${this.groupId}`
@ -215,27 +209,11 @@
flex-direction: column;
margin: 8rpx 2rpx;
position: relative;
align-items: center;
padding-right: 5px;
background-color: #fafafa;
white-space: nowrap;
.member-avatar {
display: flex;
justify-content: center;
align-items: center;
width: 100rpx;
height: 100rpx;
.head-image {
width: 100%;
height: 100%;
border-radius: 10%;
border: #d8d8d8 solid 1px;
}
}
.member-name {
width: 100%;
flex: 1;

37
im-uniapp/pages/group/group-invite.vue

@ -7,12 +7,19 @@
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="friend in friendItems" v-show="!searchText || friend.nickName.startsWith(searchText)"
:key="friend.id">
<uni-list-chat :avatar="friend.headImage" :title="friend.nickName" :clickable="true"
@click="onSwitchChecked(friend)">
<view class="chat-custom-right">
<view class="friend-item" @click="onSwitchChecked(friend)">
<head-image :name="friend.nickName"
:online="friend.online" :url="friend.headImage"
:size="100"></head-image>
<view class="friend-name">{{ friend.nickName}}</view>
<view class="friend-checked">
<radio :checked="friend.checked" :disabled="friend.disabled" @click.stop="onSwitchChecked(friend)"/>
</view>
</uni-list-chat>
</view>
</view>
</scroll-view>
</view>
@ -83,6 +90,7 @@
id: f.id,
headImage: f.headImage,
nickName: f.nickName,
online: f.online
}
item.disabled = this.isGroupMember(f.id);
item.checked = item.disabled;
@ -127,6 +135,27 @@
flex: 1;
overflow: hidden;
.friend-item {
height: 120rpx;
display: flex;
margin-bottom: 1rpx;
position: relative;
padding: 0 30rpx ;
align-items: center;
background-color: white;
white-space: nowrap;
.friend-name {
flex:1;
padding-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
}
}
.scroll-bar {
height: 100%;
}

43
im-uniapp/pages/group/group-member.vue

@ -7,13 +7,18 @@
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="(member,idx) in groupMembers"
v-show="!searchText || member.aliasName.startsWith(searchText)" :key="idx">
<uni-list-chat :avatar="member.headImage" :title="member.aliasName" :clickable="true"
@click="onShowUserInfo(member.userId)">
<view class="chat-custom-right">
<view class="member-item" @click="onShowUserInfo(member.userId)">
<head-image :name="member.aliasName"
:online="member.online" :url="member.headImage"
:size="100"></head-image>
<view class="member-name">{{ member.aliasName}}</view>
<view class="member-kick">
<button type="warn" v-show="isOwner && !isSelf(member.userId)" size="mini"
@click.stop="onKickOut(member,idx)">移出群聊</button>
</view>
</uni-list-chat>
</view>
</view>
</scroll-view>
</view>
@ -41,7 +46,7 @@
title: '确认移出?',
content: `确定将成员'${member.aliasName}'移出群聊吗?`,
success: (res) => {
if(res.cancel)
if (res.cancel)
return;
this.$http({
url: `/group/kick/${this.group.id}?userId=${member.userId}`,
@ -87,7 +92,7 @@
this.loadGroupMembers(options.id);
},
onUnload() {
if(this.isModify){
if (this.isModify) {
//
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
@ -98,16 +103,38 @@
</script>
<style scoped lang="scss">
.group-invite{
.group-member {
position: relative;
border: #dddddd solid 1px;
display: flex;
flex-direction: column;
.member-items{
.member-items {
position: relative;
flex: 1;
overflow: hidden;
.member-item {
height: 120rpx;
display: flex;
margin-bottom: 1rpx;
position: relative;
padding: 0 30rpx ;
align-items: center;
background-color: white;
white-space: nowrap;
.member-name {
flex:1;
padding-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
}
}
.scroll-bar {
height: 100%;
}

34
im-uniapp/store/friendStore.js

@ -1,4 +1,5 @@
import http from '../common/request';
import http from '../common/request'
import {TERMINAL_TYPE} from '../common/enums.js'
export default {
@ -21,8 +22,8 @@ export default {
})
},
removeFriend(state, id) {
state.friends.forEach((f,idx) => {
if(f.id == id){
state.friends.forEach((f, idx) => {
if (f.id == id) {
state.friends.splice(idx, 1)
}
});
@ -31,10 +32,21 @@ export default {
state.friends.push(friend);
},
setOnlineStatus(state, onlineIds) {
setOnlineStatus(state, onlineTerminals) {
state.friends.forEach((f) => {
let onlineFriend = onlineIds.find((id) => f.id == id);
f.online = onlineFriend != undefined;
let userTerminal = onlineTerminals.find((o) => f.id == o.userId);
if (userTerminal) {
console.log(userTerminal)
f.online = true;
f.onlineTerminals = userTerminal.terminals;
f.onlineWeb = userTerminal.terminals.indexOf(TERMINAL_TYPE.WEB) >= 0
f.onlineApp = userTerminal.terminals.indexOf(TERMINAL_TYPE.APP) >= 0
} else {
f.online = false;
f.onlineTerminals = [];
f.onlineWeb = false;
f.onlineApp = false;
}
});
state.friends.sort((f1, f2) => {
@ -48,16 +60,16 @@ export default {
});
},
refreshOnlineStatus(state) {
if (state.friends.length > 0 ) {
if (state.friends.length > 0) {
let userIds = [];
state.friends.forEach((f) => {
userIds.push(f.id)
});
http({
url: '/user/online?userIds='+ userIds.join(','),
url: '/user/terminal/online?userIds=' + userIds.join(','),
method: 'GET'
}).then((onlineIds) => {
this.commit("setOnlineStatus", onlineIds);
}).then((onlineTerminals) => {
this.commit("setOnlineStatus", onlineTerminals);
})
}
// 30s后重新拉取
@ -66,7 +78,7 @@ export default {
this.commit("refreshOnlineStatus");
}, 30000)
},
clear(state){
clear(state) {
clearTimeout(state.timer);
state.friends = [];
state.timer = null;

Loading…
Cancel
Save