Browse Source

群聊功能开发中

master
xie.bx 3 years ago
parent
commit
317f56a9cb
  1. 13
      commom/src/main/java/com/lx/common/contant/Constant.java
  2. 12
      commom/src/main/java/com/lx/common/contant/Contant.java
  3. 9
      im-platform/src/main/java/com/lx/implatform/controller/GroupController.java
  4. 3
      im-platform/src/main/java/com/lx/implatform/service/IGroupService.java
  5. 40
      im-platform/src/main/java/com/lx/implatform/service/impl/GroupServiceImpl.java
  6. 6
      im-platform/src/main/java/com/lx/implatform/service/thirdparty/FileService.java
  7. 24
      im-platform/src/main/java/com/lx/implatform/vo/GroupMemberVO.java
  8. 2
      im-platform/src/main/resources/db/db.sql
  9. 4
      im-ui/src/assets/style/global.css
  10. 10
      im-ui/src/components/chat/ChatItem.vue
  11. 21
      im-ui/src/components/common/HeadImage.vue
  12. 2
      im-ui/src/components/friend/AddFriend.vue
  13. 29
      im-ui/src/components/friend/FriendItem.vue
  14. 182
      im-ui/src/components/group/AddGroupMember.vue
  15. 71
      im-ui/src/components/group/GroupItem.vue
  16. 47
      im-ui/src/components/group/GroupMember.vue
  17. 2
      im-ui/src/components/setting/Setting.vue
  18. 3
      im-ui/src/store/friendStore.js
  19. 3
      im-ui/src/store/groupStore.js
  20. 33
      im-ui/src/view/Chat.vue
  21. 48
      im-ui/src/view/Friend.vue
  22. 234
      im-ui/src/view/Group.vue

13
commom/src/main/java/com/lx/common/contant/Constant.java

@ -0,0 +1,13 @@
package com.lx.common.contant;
public class Constant {
// 最大图片上传大小
public static final long MAX_IMAGE_SIZE = 5*1024*1024;
// 最大上传文件大小
public static final long MAX_FILE_SIZE = 10*1024*1024;
// 群聊最大人数
public static final long MAX_GROUP_MEMBER = 500;
}

12
commom/src/main/java/com/lx/common/contant/Contant.java

@ -1,12 +0,0 @@
package com.lx.common.contant;
public class Contant {
public static final long MAX_IMAGE_SIZE = 5*1024*1024;
public static final long MAX_FILE_SIZE = 10*1024*1024;
}

9
im-platform/src/main/java/com/lx/implatform/controller/GroupController.java

@ -5,6 +5,7 @@ import com.lx.common.result.Result;
import com.lx.common.result.ResultUtils; import com.lx.common.result.ResultUtils;
import com.lx.implatform.service.IGroupService; import com.lx.implatform.service.IGroupService;
import com.lx.implatform.vo.GroupInviteVO; import com.lx.implatform.vo.GroupInviteVO;
import com.lx.implatform.vo.GroupMemberVO;
import com.lx.implatform.vo.GroupVO; import com.lx.implatform.vo.GroupVO;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@ -54,5 +55,13 @@ public class GroupController {
groupService.invite(vo); groupService.invite(vo);
return ResultUtils.success(); return ResultUtils.success();
} }
@ApiOperation(value = "查询群聊成员",notes="查询群聊成员")
@GetMapping("/members/{groupId}")
public Result<List<GroupMemberVO>> findGroupMembers(@NotNull(message = "群聊id不能为空") @PathVariable Long groupId){
return ResultUtils.success(groupService.findGroupMembers(groupId));
}
} }

3
im-platform/src/main/java/com/lx/implatform/service/IGroupService.java

@ -3,6 +3,7 @@ package com.lx.implatform.service;
import com.lx.implatform.entity.Group; import com.lx.implatform.entity.Group;
import com.baomidou.mybatisplus.extension.service.IService; import com.baomidou.mybatisplus.extension.service.IService;
import com.lx.implatform.vo.GroupInviteVO; import com.lx.implatform.vo.GroupInviteVO;
import com.lx.implatform.vo.GroupMemberVO;
import com.lx.implatform.vo.GroupVO; import com.lx.implatform.vo.GroupVO;
import java.util.List; import java.util.List;
@ -27,4 +28,6 @@ public interface IGroupService extends IService<Group> {
List<GroupVO> findGroups(); List<GroupVO> findGroups();
void invite(GroupInviteVO vo); void invite(GroupInviteVO vo);
List<GroupMemberVO> findGroupMembers(Long groupId);
} }

40
im-platform/src/main/java/com/lx/implatform/service/impl/GroupServiceImpl.java

@ -1,6 +1,7 @@
package com.lx.implatform.service.impl; package com.lx.implatform.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lx.common.contant.Constant;
import com.lx.common.enums.ResultCode; import com.lx.common.enums.ResultCode;
import com.lx.common.util.BeanUtils; import com.lx.common.util.BeanUtils;
import com.lx.implatform.entity.Friend; import com.lx.implatform.entity.Friend;
@ -17,11 +18,13 @@ import com.lx.implatform.service.IUserService;
import com.lx.implatform.session.SessionContext; import com.lx.implatform.session.SessionContext;
import com.lx.implatform.session.UserSession; import com.lx.implatform.session.UserSession;
import com.lx.implatform.vo.GroupInviteVO; import com.lx.implatform.vo.GroupInviteVO;
import com.lx.implatform.vo.GroupMemberVO;
import com.lx.implatform.vo.GroupVO; import com.lx.implatform.vo.GroupVO;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -124,6 +127,9 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
QueryWrapper<GroupMember> memberWrapper = new QueryWrapper(); QueryWrapper<GroupMember> memberWrapper = new QueryWrapper();
memberWrapper.lambda().eq(GroupMember::getUserId, session.getId()); memberWrapper.lambda().eq(GroupMember::getUserId, session.getId());
List<GroupMember> groupMembers = groupMemberService.list(memberWrapper); List<GroupMember> groupMembers = groupMemberService.list(memberWrapper);
if(groupMembers.isEmpty()){
return Collections.EMPTY_LIST;
}
// 拉取群信息 // 拉取群信息
List<Long> ids = groupMembers.stream().map((gm -> gm.getGroupId())).collect(Collectors.toList()); List<Long> ids = groupMembers.stream().map((gm -> gm.getGroupId())).collect(Collectors.toList());
QueryWrapper<Group> groupWrapper = new QueryWrapper(); QueryWrapper<Group> groupWrapper = new QueryWrapper();
@ -146,11 +152,18 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
@Override @Override
public void invite(GroupInviteVO vo) { public void invite(GroupInviteVO vo) {
UserSession session = SessionContext.getSession(); UserSession session = SessionContext.getSession();
// 群聊人数校验
QueryWrapper<GroupMember> memberWrapper = new QueryWrapper();
memberWrapper.lambda().eq(GroupMember::getGroupId, vo.getGroupId());
List<GroupMember> members = groupMemberService.list(memberWrapper);
if(vo.getFriendIds().size() + members.size() > Constant.MAX_GROUP_MEMBER){
throw new GlobalException(ResultCode.PROGRAM_ERROR, "群聊人数不能大于"+Constant.MAX_GROUP_MEMBER+"人");
}
// 已经在群里面用户,不可重复加入 // 已经在群里面用户,不可重复加入
QueryWrapper<GroupMember> wrapper = new QueryWrapper(); Boolean flag = vo.getFriendIds().stream().anyMatch(id->{
wrapper.lambda().eq(GroupMember::getId, vo.getGroupId()) return members.stream().anyMatch(m->m.getUserId()==id);
.in(GroupMember::getUserId, vo.getFriendIds()); });
if(groupMemberService.count(wrapper)>0){ if(flag){
throw new GlobalException(ResultCode.PROGRAM_ERROR, "部分用户已经在群中,邀请失败"); throw new GlobalException(ResultCode.PROGRAM_ERROR, "部分用户已经在群中,邀请失败");
} }
// 找出好友信息 // 找出好友信息
@ -174,4 +187,23 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
groupMemberService.saveBatch(groupMembers); groupMemberService.saveBatch(groupMembers);
} }
} }
/**
* 查询群成员
*
* @Param groupId 群聊id
* @return List<GroupMemberVO>
**/
@Override
public List<GroupMemberVO> findGroupMembers(Long groupId) {
QueryWrapper<GroupMember> memberWrapper = new QueryWrapper();
memberWrapper.lambda().eq(GroupMember::getGroupId, groupId);
List<GroupMember> members = groupMemberService.list(memberWrapper);
List<GroupMemberVO> vos = members.stream().map(m->{
GroupMemberVO vo = BeanUtils.copyProperties(m,GroupMemberVO.class);
return vo;
}).collect(Collectors.toList());
return vos;
}
} }

6
im-platform/src/main/java/com/lx/implatform/service/thirdparty/FileService.java

@ -1,6 +1,6 @@
package com.lx.implatform.service.thirdparty; package com.lx.implatform.service.thirdparty;
import com.lx.common.contant.Contant; import com.lx.common.contant.Constant;
import com.lx.common.enums.FileTypeEnum; import com.lx.common.enums.FileTypeEnum;
import com.lx.common.enums.ResultCode; import com.lx.common.enums.ResultCode;
import com.lx.implatform.exception.GlobalException; import com.lx.implatform.exception.GlobalException;
@ -51,7 +51,7 @@ public class FileService {
public String uploadFile(MultipartFile file){ public String uploadFile(MultipartFile file){
// 大小校验 // 大小校验
if(file.getSize() > Contant.MAX_FILE_SIZE){ if(file.getSize() > Constant.MAX_FILE_SIZE){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"文件大小不能超过10M"); throw new GlobalException(ResultCode.PROGRAM_ERROR,"文件大小不能超过10M");
} }
// 上传 // 上传
@ -65,7 +65,7 @@ public class FileService {
public UploadImageVO uploadImage(MultipartFile file){ public UploadImageVO uploadImage(MultipartFile file){
try { try {
// 大小校验 // 大小校验
if(file.getSize() > Contant.MAX_IMAGE_SIZE){ if(file.getSize() > Constant.MAX_IMAGE_SIZE){
throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片大小不能超过5M"); throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片大小不能超过5M");
} }
// 图片格式校验 // 图片格式校验

24
im-platform/src/main/java/com/lx/implatform/vo/GroupMemberVO.java

@ -0,0 +1,24 @@
package com.lx.implatform.vo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@Data
@ApiModel("群成员信息VO")
public class GroupMemberVO {
@ApiModelProperty("用户id")
private Long userId;
@ApiModelProperty("群内显示名称")
private String aliasName;
@ApiModelProperty("头像")
private String headImage;
@ApiModelProperty("备注")
private String remark;
}

2
im-platform/src/main/resources/db/db.sql

@ -7,7 +7,7 @@ create table `im_user`(
`head_image_thumb` varchar(255) default '' comment '用户头像缩略图', `head_image_thumb` varchar(255) default '' comment '用户头像缩略图',
`password` varchar(255) not null comment '密码(明文)', `password` varchar(255) not null comment '密码(明文)',
`sex` tinyint(1) default 0 comment '性别 0:男 1::女', `sex` tinyint(1) default 0 comment '性别 0:男 1::女',
`signature` varchar(1024) not null comment '个性签名', `signature` varchar(1024) default '' comment '个性签名',
`last_login_time` datetime DEFAULT null comment '最后登录时间', `last_login_time` datetime DEFAULT null comment '最后登录时间',
`created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间', `created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
unique key `idx_user_name`(user_name), unique key `idx_user_name`(user_name),

4
im-ui/src/assets/style/global.css

@ -17,7 +17,9 @@ section {
height: 100%; height: 100%;
} }
.el-dialog__body{
padding: 10px 15px !important;
}
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 6px;

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

@ -13,7 +13,6 @@
</div> </div>
<div class="right "> <div class="right ">
<div @click.stop="onClickClose()"><i class="el-icon-close close" style="border: none; font-size: 20px;color: black;" title="关闭"></i></div> <div @click.stop="onClickClose()"><i class="el-icon-close close" style="border: none; font-size: 20px;color: black;" title="关闭"></i></div>
<div class="msg-time"> <div class="msg-time">
<chat-time :time="chat.lastSendTime"></chat-time> <chat-time :time="chat.lastSendTime"></chat-time>
</div> </div>
@ -64,14 +63,14 @@
padding-left: 15px; padding-left: 15px;
align-items: center; align-items: center;
padding-right: 5px; padding-right: 5px;
background-color: #eeeeee; background-color: #fafafa;
&:hover { &:hover {
background-color: #dddddd; background-color: #eeeeee;
} }
&.active { &.active {
background-color: #cccccc; background-color: #dddddd;
} }
@ -159,7 +158,4 @@
} }
} }
.active {
background-color: #eeeeee;
}
</style> </style>

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

@ -1,8 +1,6 @@
<template> <template>
<div class='img-box'> <img :src="url"
<img :src="url" :style="{width: size+'px',height: size+'px',cursor: 'pointer'}" />
style="width: 100%;height: 100%;cursor: pointer;" />
</div>
</template> </template>
<script> <script>
@ -27,28 +25,19 @@
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.img-box {
width: 100%;
height: 100%;
display: inline-block;
border-radius: 3px;
background-color: #c0c4cc;
overflow: hidden;
img { img {
position: relative; position: relative;
overflow: hidden;
border-radius: 5%;
} }
img:before { img:before {
content: ''; content: '';
display: block; display: block;
width: 100%; width: 100%;
height: 100%; height: 100%;
background:url('../../assets/default_head.png') no-repeat 0 0; background:url('../../assets/default_head.png') no-repeat 0 0;
background-size: 100%; background-size: 100%;
} }
}
</style> </style>

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

@ -4,7 +4,7 @@
<el-button slot="append" icon="el-icon-search" @click="handleSearch()"></el-button> <el-button slot="append" icon="el-icon-search" @click="handleSearch()"></el-button>
</el-input> </el-input>
<el-scrollbar style="height:400px"> <el-scrollbar style="height:400px">
<div v-for="(user) in users" :key="user.id"> <div v-for="(user) in users" :key="user.id" v-show="user.id != $store.state.userStore.userInfo.id">
<div class="item"> <div class="item">
<div class="avatar"> <div class="avatar">
<head-image :url="user.headImage"></head-image> <head-image :url="user.headImage"></head-image>

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

@ -1,5 +1,5 @@
<template> <template>
<div class="item" :class="active ? 'active' : ''"> <div class="friend-item" :class="active ? 'active' : ''">
<div class="avatar"> <div class="avatar">
<head-image :url="friend.headImage"> </head-image> <head-image :url="friend.headImage"> </head-image>
</div> </div>
@ -7,9 +7,10 @@
<div>{{ friend.nickName}}</div> <div>{{ friend.nickName}}</div>
<div :class="online ? 'online-status online':'online-status'">{{ online?"[在线]":"[离线]"}}</div> <div :class="online ? 'online-status online':'online-status'">{{ online?"[在线]":"[离线]"}}</div>
</div> </div>
<div class="close" @click.stop="$emit('del',friend,index)"> <div v-if="showDelete" class="close" @click.stop="handleDel()">
<i class="el-icon-close" style="border: none; font-size: 20px;color: black;" title="添加好友"></i> <i class="el-icon-close" style="border: none; font-size: 20px;color: black;" title="添加好友"></i>
</div> </div>
<slot></slot>
</div> </div>
</template> </template>
@ -24,6 +25,12 @@
data() { data() {
return {} return {}
}, },
methods:{
handleDel(){
console.log("11111111111111111111")
this.$emit('del',this.friend,this.index)
}
},
props: { props: {
friend: { friend: {
type: Object type: Object
@ -33,6 +40,10 @@
}, },
index: { index: {
type: Number type: Number
},
showDelete:{
type: Boolean,
default: true
} }
}, },
computed: { computed: {
@ -44,7 +55,7 @@
</script> </script>
<style scope lang="scss"> <style scope lang="scss">
.item { .friend-item {
height: 65px; height: 65px;
display: flex; display: flex;
margin-bottom: 1px; margin-bottom: 1px;
@ -52,14 +63,14 @@
padding-left: 15px; padding-left: 15px;
align-items: center; align-items: center;
padding-right: 5px; padding-right: 5px;
background-color: #eeeeee; background-color: #fafafa;
&:hover { &:hover {
background-color: #dddddd; background-color: #eeeeee;
} }
&.active { &.active {
background-color: #cccccc; background-color: #dddddd;
} }
@ -88,7 +99,7 @@
.text { .text {
margin-left: 15px; margin-left: 15px;
flex: 3; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-around; justify-content: space-around;
@ -111,8 +122,4 @@
} }
} }
} }
.active {
background-color: #eeeeee;
}
</style> </style>

182
im-ui/src/components/group/AddGroupMember.vue

@ -0,0 +1,182 @@
<template>
<el-dialog title="邀请好友" :visible.sync="visible" v-if="visible" width="50%" :before-close="handleClose">
<div class="agm-container">
<div class="agm-l-box">
<el-input width="200px" placeholder="搜索好友" class="input-with-select" v-model="searchText" @keyup.enter.native="handleSearch()">
<el-button slot="append" icon="el-icon-search" @click="handleSearch()"></el-button>
</el-input>
<el-scrollbar style="height:500px;">
<div v-for="(friend,index) in friends" :key="friend.id">
<friend-item v-show="friend.nickName.startsWith(searchText)"
:showDelete="false" @click.native="handleSwitchCheck(friend)" :friend="friend"
:index="index" :active="index === activeIndex">
<el-checkbox :disabled="friend.disabled" @click.native.stop="" class="agm-friend-checkbox" v-model="friend.isCheck"
size="medium"></el-checkbox>
</friend-item>
</div>
</el-scrollbar>
</div>
<div class="agm-r-box">
<div class="agm-select-tip"> 已勾选{{checkCount}}位好友</div>
<el-scrollbar style="height:500px;">
<div v-for="(friend,index) in friends" :key="friend.id">
<friend-item v-if="friend.isCheck && !friend.disabled" :friend="friend" :index="index" :active="false" @del="handleRemoveFriend(friend,index)">
</friend-item>
</div>
</el-scrollbar>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button @click="handleClose()"> </el-button>
<el-button type="primary" @click="handleOk()"> </el-button>
</span>
</el-dialog>
</template>
<script>
import FriendItem from '../friend/FriendItem.vue';
export default {
name: "addGroupMember",
components: {
FriendItem
},
data() {
return {
searchText: "",
activeIndex: -1,
friends: []
}
},
methods: {
handleClose() {
this.$emit("close");
},
handleOk() {
let inviteVO = {
groupId: this.groupId,
friendIds: []
}
this.friends.forEach((f) => {
if (f.isCheck && !f.disabled) {
inviteVO.friendIds.push(f.id);
}
})
if (inviteVO.friendIds.length > 0) {
this.$http({
url: "/api/group/invite",
method: 'post',
data: inviteVO
}).then(() => {
this.$message.success("邀请成功");
this.$emit("reload");
this.$emit("close");
})
}
},
handleRemoveFriend(friend, index) {
friend.isCheck = false;
},
handleSwitchCheck(friend) {
if (!friend.disabled) {
friend.isCheck = !friend.isCheck
}
}
},
props: {
visible: {
type: Boolean
},
groupId: {
type: Number
},
members: {
type: Array
}
},
computed: {
checkCount() {
return this.friends.filter((f) => f.isCheck && !f.disabled).length;
}
},
watch: {
visible: function(newData, oldData) {
if (newData) {
this.friends = [];
this.$store.state.friendStore.friends.forEach((f) => {
let friend = JSON.parse(JSON.stringify(f))
let m = this.members.find((m) => m.userId == f.id);
console.log(m);
if (m) {
//
friend.disabled = true;
friend.isCheck = true
} else {
friend.disabled = false;
friend.isCheck = false;
}
this.friends.push(friend);
})
}
}
}
}
</script>
<style lang="scss">
.agm-container {
display: flex;
.agm-l-box {
flex: 1;
border: #dddddd solid 1px;
.el-checkbox {
display: flex;
align-items: center;
//
.el-checkbox__inner {
width: 20px;
height: 20px;
//
&::after {
height: 12px;
left: 7px;
}
}
//
.el-checkbox__input.is-checked+.el-checkbox__label {
color: #333333;
}
.el-checkbox__label {
line-height: 20px;
padding-left: 8px;
}
}
.agm-friend-checkbox {
margin-right: 20px;
}
}
.agm-r-box {
flex: 1;
border: #dddddd solid 1px;
.agm-select-tip {
text-align: left;
height: 40px;
line-height: 40px;
text-indent: 5px;
}
}
}
</style>

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

@ -0,0 +1,71 @@
<template>
<div class="item" :class="active ? 'active' : ''">
<div class="avatar">
<head-image :url="group.headImage"> </head-image>
</div>
<div class="text">
<div>{{group.name}}</div>
</div>
</div>
</template>
<script>
import HeadImage from '../common/HeadImage.vue';
export default {
name: "groupItem",
components: {
HeadImage
},
data() {
return {}
},
props: {
group: {
type: Object
},
active: {
type: Boolean
}
}
}
</script>
<style lang="scss" >
.item {
height: 65px;
display: flex;
margin-bottom: 1px;
position: relative;
padding-left: 15px;
align-items: center;
padding-right: 5px;
background-color: #fafafa;
&:hover {
background-color: #eeeeee;
}
&.active {
background-color: #dddddd;
}
.avatar {
width: 45px;
height: 45px;
}
.text {
display: flex;
flex-direction: column;
justify-content: space-around;
flex: 1;
margin-left: 15px;
height: 100%;
flex-shrink: 0;
overflow: hidden;
text-align: left;
}
}
</style>

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

@ -0,0 +1,47 @@
<template>
<div class="group-member">
<head-image :url="member.headImage" :size="60" class=""></head-image>
<div class="member-name">{{member.aliasName}}121212121212</div>
</div>
</template>
<script>
import HeadImage from "../common/HeadImage.vue";
export default{
name: "groupMember",
components:{HeadImage},
data(){
return {};
},
props:{
member:{
type: Object,
required: true
},
showDel:{
type: Boolean,
default: true
}
}
}
</script>
<style lang="scss">
.group-member{
display: flex;
flex-direction: column;
align-items: center;
width: 60px;
.member-name {
font-size: 16px;
text-align: center;
width: 100%;
height: 30px;
line-height: 30px;
white-space: nowrap;
text-overflow:ellipsis;
overflow:hidden
}
}
</style>

2
im-ui/src/components/setting/Setting.vue

@ -105,7 +105,7 @@
<style lang="scss" > <style lang="scss" >
.setting { .setting {
.avatar-uploader { .avatar-uploader {
.el-upload { .el-upload {
border: 1px dashed #d9d9d9 !important; border: 1px dashed #d9d9d9 !important;
border-radius: 6px; border-radius: 6px;

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

@ -33,6 +33,9 @@ export default {
}, },
removeFriend(state, index) { removeFriend(state, index) {
state.friends.splice(index, 1); state.friends.splice(index, 1);
if(state.activeIndex >= state.friends.length){
state.activeIndex = state.friends.length-1;
}
}, },
addFriend(state, friend) { addFriend(state, friend) {
state.friends.push(friend); state.friends.push(friend);

3
im-ui/src/store/groupStore.js

@ -17,6 +17,9 @@ export default {
}, },
setGroups(state,groups){ setGroups(state,groups){
state.groups = groups; state.groups = groups;
},
activeGroup(state,index){
state.activeIndex = index;
} }
} }
} }

33
im-ui/src/view/Chat.vue

@ -1,18 +1,14 @@
<template> <template>
<el-container> <el-container>
<el-aside width="250px" class="l-chat-box"> <el-aside width="250px" class="l-chat-box">
<el-header height="60px"> <div class="l-chat-header">
<el-row> <el-input width="200px" placeholder="搜索" v-model="searchText">
<el-input width="200px" placeholder="搜索" v-model="searchText"> <el-button slot="append" icon="el-icon-search"></el-button>
<el-button slot="append" icon="el-icon-search"></el-button> </el-input>
</el-input> </div>
</el-row> <div v-for="(chat,index) in chatStore.chats" :key="chat.targetId">
</el-header> <chat-item :chat="chat" :index="index" @click.native="handleActiveItem(index)" @del="handleDelItem(chat,index)" :active="index === chatStore.activeIndex"></chat-item>
<el-main> </div>
<div v-for="(chat,index) in chatStore.chats" :key="chat.targetId">
<chat-item :chat="chat" :index="index" @click.native="handleActiveItem(index)" @del="handleDelItem(chat,index)" :active="index === chatStore.activeIndex"></chat-item>
</div>
</el-main>
</el-aside> </el-aside>
<el-container class="r-chat-box"> <el-container class="r-chat-box">
<el-header height="60px"> <el-header height="60px">
@ -308,37 +304,28 @@
<style lang="scss"> <style lang="scss">
.el-container { .el-container {
.l-chat-box { .l-chat-box {
border: #dddddd solid 1px; border: #dddddd solid 1px;
background: #eeeeee; background: white;
width: 3rem; width: 3rem;
.l-chat-header {
.el-header {
padding: 5px; padding: 5px;
background-color: white; background-color: white;
line-height: 50px; line-height: 50px;
} }
.el-main {
padding: 0
}
} }
.r-chat-box { .r-chat-box {
background: white; background: white;
border: #dddddd solid 1px; border: #dddddd solid 1px;
.el-header { .el-header {
padding: 5px; padding: 5px;
background-color: white; background-color: white;
line-height: 50px; line-height: 50px;
} }
.im-chat-main { .im-chat-main {
padding: 0; padding: 0;
border: #dddddd solid 1px; border: #dddddd solid 1px;
.im-chat-box { .im-chat-box {
ul { ul {
padding: 20px; padding: 20px;

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

@ -1,29 +1,28 @@
<template> <template>
<el-container> <el-container>
<el-aside width="250px" class="l-friend-box"> <el-aside width="250px" class="l-friend-box">
<el-header class="l-friend-header" height="60px"> <div class="l-friend-header" height="60px">
<div class="l-friend-search"> <div class="l-friend-search">
<el-input width="200px" placeholder="搜索好友" v-model="searchText"> <el-input width="200px" placeholder="搜索好友" v-model="searchText">
<el-button slot="append" icon="el-icon-search"></el-button> <el-button slot="append" icon="el-icon-search"></el-button>
</el-input> </el-input>
</div> </div>
<el-button plain icon="el-icon-plus" style="border: none; padding:12px; font-size: 20px;color: black;" title="添加好友" @click="handleShowAddFriend()"></el-button> <el-button plain icon="el-icon-plus" style="border: none; padding:12px; font-size: 20px;color: black;" title="添加好友"
@click="handleShowAddFriend()"></el-button>
<add-friend :dialogVisible="showAddFriend" @close="handleCloseAddFriend"> <add-friend :dialogVisible="showAddFriend" @close="handleCloseAddFriend">
</add-friend> </add-friend>
</el-header> </div>
<el-main> <div v-for="(friend,index) in $store.state.friendStore.friends" :key="friend.id">
<div v-for="(friend,index) in $store.state.friendStore.friends" :key="friend.id"> <friend-item v-show="friend.nickName.startsWith(searchText)" :friend="friend" :index="index" :active="index === $store.state.friendStore.activeIndex"
<friend-item v-show="friend.nickName.startsWith(searchText)" :friend="friend" :index="index" @del="handleDelItem(friend,index)" @click.native="handleActiveItem(friend,index)">
:active="index === $store.state.friendStore.activeIndex" @del="handleDelItem(friend,index)" @click.native="handleActiveItem(friend,index)"> </friend-item>
</friend-item> </div>
</div>
</el-main>
</el-aside> </el-aside>
<el-container class="r-friend-box"> <el-container class="r-friend-box">
<div v-show="$store.state.friendStore.activeIndex>=0"> <div v-show="activeUser.id">
<div class="user-detail"> <div class="user-detail">
<head-image class="detail-head-image" :url="activeUser.headImage"></head-image> <head-image class="detail-head-image" :size="200" :url="activeUser.headImage"></head-image>
<div class="info-item"> <div class="info-item">
<el-descriptions title="好友信息" class="description" :column="1"> <el-descriptions title="好友信息" class="description" :column="1">
<el-descriptions-item label="用户名">{{ activeUser.userName }} <el-descriptions-item label="用户名">{{ activeUser.userName }}
@ -103,6 +102,7 @@
showName: user.nickName, showName: user.nickName,
headImage: user.headImage, headImage: user.headImage,
}; };
console.log(chat);
this.$store.commit("openChat", chat); this.$store.commit("openChat", chat);
this.$store.commit("activeChat", 0); this.$store.commit("activeChat", 0);
this.$router.push("/home/chat"); this.$router.push("/home/chat");
@ -128,25 +128,19 @@
.el-container { .el-container {
.l-friend-box { .l-friend-box {
border: #dddddd solid 1px; border: #dddddd solid 1px;
background: #eeeeee; background: white;
.l-friend-header { .l-friend-header {
display: flex; display: flex;
align-items: center; align-items: center;
padding: 5px; padding: 5px;
background-color: white; background-color: white;
.l-friend-search{ .l-friend-search {
flex: 1; flex: 1;
} }
} }
.el-main {
padding: 0;
}
} }
.r-friend-box { .r-friend-box {
.user-detail { .user-detail {
width: 100%; width: 100%;
@ -154,31 +148,19 @@
padding: 50px 10px 10px 50px; padding: 50px 10px 10px 50px;
text-align: center; text-align: center;
justify-content: space-around; justify-content: space-around;
.detail-head-image {
width: 200px;
height: 200px;
}
.info-item { .info-item {
width: 400px; width: 400px;
height: 200px; height: 200px;
background-color: #ffffff; background-color: #ffffff;
} }
.description { .description {
padding: 20px 20px 0px 20px; padding: 20px 20px 0px 20px;
} }
} }
.btn-group { .btn-group {
text-align: left !important; text-align: left !important;
padding-left: 100px; padding-left: 100px;
} }
} }
} }
</style> </style>

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

@ -1,7 +1,7 @@
<template> <template>
<el-container class="im-group-box"> <el-container class="im-group-box">
<el-aside width="250px" class="l-group-box"> <el-aside width="250px" class="l-group-box">
<el-header class="l-group-header" height="60px"> <div class="l-group-header">
<div class="l-group-search"> <div class="l-group-search">
<el-input width="200px" placeholder="搜索群聊" v-model="searchText"> <el-input width="200px" placeholder="搜索群聊" v-model="searchText">
<el-button slot="append" icon="el-icon-search"></el-button> <el-button slot="append" icon="el-icon-search"></el-button>
@ -9,19 +9,68 @@
</div> </div>
<el-button plain icon="el-icon-plus" style="border: none; padding: 12px; font-size: 20px;color: black;" title="创建群聊" <el-button plain icon="el-icon-plus" style="border: none; padding: 12px; font-size: 20px;color: black;" title="创建群聊"
@click="handleCreateGroup()"></el-button> @click="handleCreateGroup()"></el-button>
</el-header> </div>
<el-main>
<el-main> <div v-for="(group,index) in groupStore.groups" :key="group.id">
<div v-for="(group,index) in groupStore.groups" :key="group.id"> <group-item v-show="group.name.startsWith(searchText)" :group="group" :active="index === groupStore.activeIndex"
<group-item v-show="group.name.startsWith(searchText)" :group="group" :index="index" :active="index === groupStore.activeIndex" @click.native="handleActiveItem(group,index)">
@click.native="handleActiveItem(group,index)"> </group-item>
</group-item> </div>
</div>
</el-main>
</el-main>
</el-aside> </el-aside>
<el-container class="r-chat-box"> <el-container class="r-group-box">
group <div class="r-group-header">
{{activeGroup.name}}
</div>
<div class="r-group-container">
<div v-show="groupStore.activeIndex>=0">
<div class="r-group-info">
<file-upload class="avatar-uploader" action="/api/image/upload" :showLoading="true" :maxSize="maxSize" @success="handleUploadSuccess"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg']">
<img v-if="activeGroup.headImage" :src="activeGroup.headImage" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</file-upload>
<el-form class="r-group-form" label-width="130px" :model="activeGroup">
<el-form-item label="群聊名称">
<el-input v-model="activeGroup.name"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="activeGroup.remark" placeholder="群聊的备注仅自己可见"></el-input>
</el-form-item>
<el-form-item label="我在本群的昵称">
<el-input v-model="activeGroup.aliasName" placeholder=""></el-input>
</el-form-item>
<el-form-item label="群公告">
<el-input v-model="activeGroup.notice" type="textarea" placeholder="群主未设置"></el-input>
</el-form-item>
</el-form>
</div>
<div class="btn-group">
<el-button class="send-btn" @click="handleSendMessage()">保存</el-button>
<el-button class="send-btn" @click="handleSendMessage()">发消息</el-button>
<el-button type="danger" class="send-btn" @click="handleSendMessage()">退出</el-button>
<el-button type="danger" class="send-btn" @click="handleSendMessage()">解散</el-button>
</div>
<el-divider content-position="center"></el-divider>
<el-scrollbar style="height:400px;">
<div class="r-group-member-list">
<div v-for="(member) in groupMembers" :key="member.id">
<group-member class="r-group-member" :member="member" :showDel="true"></group-member>
</div>
<div class="r-group-invite">
<div class="invite-member-btn" title="邀请好友进群聊" @click="handleInviteMember()">
<i class="el-icon-plus"></i>
</div>
<div class="invite-member-text">邀请</div>
<add-group-member :visible="showAddGroupMember"
:groupId="activeGroup.id"
:members="groupMembers"
@reload="loadGroupMembers"
@close="handleCloseAddGroupMember"></add-group-member>
</div>
</div>
</el-scrollbar>
</div>
</div>
</el-container> </el-container>
</el-container> </el-container>
</template> </template>
@ -29,15 +78,24 @@
<script> <script>
import GroupItem from '../components/group/GroupItem'; import GroupItem from '../components/group/GroupItem';
import FileUpload from '../components/common/FileUpload';
import GroupMember from '../components/group/GroupMember.vue';
import AddGroupMember from '../components/group/AddGroupMember.vue';
export default { export default {
name: "group", name: "group",
components: { components: {
GroupItem GroupItem,
GroupMember,
FileUpload,
AddGroupMember
}, },
data() { data() {
return { return {
searchText: "" searchText: "",
maxSize: 5 * 1024 * 1024,
groupMembers:[],
showAddGroupMember: false
}; };
}, },
methods: { methods: {
@ -58,11 +116,47 @@
}, },
handleActiveItem(group, index) { handleActiveItem(group, index) {
this.$store.commit("activeGroup", index); this.$store.commit("activeGroup", index);
//
this.loadGroupMembers();
},
handleInviteMember(){
this.showAddGroupMember = true;
},
handleCloseAddGroupMember(){
this.showAddGroupMember = false;
},
handleUploadSuccess() {
},
handleSendMessage() {
},
loadGroupMembers(){
this.$http({
url: `/api/group/members/${this.activeGroup.id}`,
method: "get"
}).then((members)=>{
this.groupMembers = members;
})
} }
}, },
computed: { computed: {
groupStore() { groupStore() {
return this.$store.state.groupStore; return this.$store.state.groupStore;
},
activeGroup() {
if (this.groupStore.activeIndex >= 0) {
return this.groupStore.groups[this.groupStore.activeIndex];
}
return this.emptyGroup;
},
emptyGroup() {
return {
empty: true,
name: ""
}
} }
} }
} }
@ -72,18 +166,122 @@
.im-group-box { .im-group-box {
.l-group-box { .l-group-box {
border: #dddddd solid 1px; border: #dddddd solid 1px;
background: #eeeeee; background: white;
.l-group-header { .l-group-header {
height: 50px;
display: flex; display: flex;
align-items: center; align-items: center;
padding: 5px; padding: 5px;
background-color: white; background-color: white;
.l-group-search { .l-group-search {
flex: 1; flex: 1;
} }
} }
} }
.r-group-box {
display: flex;
flex-direction: column;
border: #dddddd solid 1px;
.r-group-header {
width: 100%;
height: 50px;
padding: 5px;
line-height: 50px;
font-size: 22px;
background-color: white;
border: #dddddd solid 1px;
}
.r-group-container {
padding: 20px;
.r-group-info {
display: flex;
padding: 20px;
.r-group-form {
flex: 1;
padding-left: 20px;
}
.avatar-uploader {
text-align: left;
.el-upload {
border: 1px dashed #d9d9d9 !important;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.el-upload:hover {
border-color: #409EFF;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
display: block;
}
}
}
.r-group-member-list{
padding: 20px;
display: flex;
align-items: center;
flex-wrap: wrap;
font-size: 16px;
text-align: center;
.r-group-member {
margin-right: 15px ;
}
.r-group-invite{
display: flex;
flex-direction: column;
align-items: center;
width: 60px;
.invite-member-btn{
width: 100%;
height: 60px;
line-height: 60px;
border: #cccccc solid 1px;
font-size: 25px;
cursor: pointer;
box-sizing: border-box;
&:hover{
border: #aaaaaa solid 1px;
}
}
.invite-member-text {
font-size: 16px;
text-align: center;
width: 100%;
height: 30px;
line-height: 30px;
white-space: nowrap;
text-overflow:ellipsis;
overflow:hidden
}
}
}
}
}
} }
</style> </style>

Loading…
Cancel
Save