Browse Source

群聊功能开发中

xie.bx 3 years ago
parent
commit
317f56a9cb

+ 13 - 0
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;
+}

+ 0 - 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 - 0
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.implatform.service.IGroupService;
 import com.lx.implatform.vo.GroupInviteVO;
+import com.lx.implatform.vo.GroupMemberVO;
 import com.lx.implatform.vo.GroupVO;
 import io.swagger.annotations.ApiOperation;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -54,5 +55,13 @@ public class GroupController {
         groupService.invite(vo);
         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 - 0
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.baomidou.mybatisplus.extension.service.IService;
 import com.lx.implatform.vo.GroupInviteVO;
+import com.lx.implatform.vo.GroupMemberVO;
 import com.lx.implatform.vo.GroupVO;
 
 import java.util.List;
@@ -27,4 +28,6 @@ public interface IGroupService extends IService<Group> {
     List<GroupVO>  findGroups();
 
     void invite(GroupInviteVO vo);
+
+    List<GroupMemberVO> findGroupMembers(Long groupId);
 }

+ 36 - 4
im-platform/src/main/java/com/lx/implatform/service/impl/GroupServiceImpl.java

@@ -1,6 +1,7 @@
 package com.lx.implatform.service.impl;
 
 import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
+import com.lx.common.contant.Constant;
 import com.lx.common.enums.ResultCode;
 import com.lx.common.util.BeanUtils;
 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.UserSession;
 import com.lx.implatform.vo.GroupInviteVO;
+import com.lx.implatform.vo.GroupMemberVO;
 import com.lx.implatform.vo.GroupVO;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.stream.Collectors;
 
@@ -124,6 +127,9 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
         QueryWrapper<GroupMember> memberWrapper = new QueryWrapper();
         memberWrapper.lambda().eq(GroupMember::getUserId, session.getId());
         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());
         QueryWrapper<Group> groupWrapper = new QueryWrapper();
@@ -146,11 +152,18 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
     @Override
     public void invite(GroupInviteVO vo) {
         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();
-        wrapper.lambda().eq(GroupMember::getId, vo.getGroupId())
-                .in(GroupMember::getUserId, vo.getFriendIds());
-        if(groupMemberService.count(wrapper)>0){
+        Boolean flag = vo.getFriendIds().stream().anyMatch(id->{
+           return  members.stream().anyMatch(m->m.getUserId()==id);
+        });
+        if(flag){
             throw new GlobalException(ResultCode.PROGRAM_ERROR, "部分用户已经在群中,邀请失败");
         }
         // 找出好友信息
@@ -174,4 +187,23 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
             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;
+    }
 }

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

@@ -1,6 +1,6 @@
 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.ResultCode;
 import com.lx.implatform.exception.GlobalException;
@@ -51,7 +51,7 @@ public class FileService {
 
     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");
         }
         // 上传
@@ -65,7 +65,7 @@ public class FileService {
     public UploadImageVO uploadImage(MultipartFile file){
         try {
             // 大小校验
-            if(file.getSize() > Contant.MAX_IMAGE_SIZE){
+            if(file.getSize() > Constant.MAX_IMAGE_SIZE){
                 throw new GlobalException(ResultCode.PROGRAM_ERROR,"图片大小不能超过5M");
             }
             // 图片格式校验

+ 24 - 0
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;
+
+}

+ 1 - 1
im-platform/src/main/resources/db/db.sql

@@ -7,7 +7,7 @@ create table `im_user`(
     `head_image_thumb` varchar(255) default '' comment '用户头像缩略图',
     `password` varchar(255) not null comment '密码(明文)',
     `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 '最后登录时间',
     `created_time` datetime DEFAULT CURRENT_TIMESTAMP comment '创建时间',
     unique key `idx_user_name`(user_name),

+ 3 - 1
im-ui/src/assets/style/global.css

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

+ 3 - 7
im-ui/src/components/chat/ChatItem.vue

@@ -13,7 +13,6 @@
 		</div>
 		<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 class="msg-time">
 				<chat-time :time="chat.lastSendTime"></chat-time>
 			</div>
@@ -64,14 +63,14 @@
 		padding-left: 15px;
 		align-items: center;
 		padding-right: 5px;
-		background-color: #eeeeee;
+		background-color: #fafafa;
 
 		&:hover {
-			background-color: #dddddd;
+			background-color: #eeeeee;
 		}
 
 		&.active {
-			background-color: #cccccc;
+			background-color: #dddddd;
 		}
 
 
@@ -159,7 +158,4 @@
 		}
 	}
 
-	.active {
-		background-color: #eeeeee;
-	}
 </style>

+ 5 - 16
im-ui/src/components/common/HeadImage.vue

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

+ 1 - 1
im-ui/src/components/friend/AddFriend.vue

@@ -4,7 +4,7 @@
 			<el-button slot="append" icon="el-icon-search" @click="handleSearch()"></el-button>
 		</el-input>
 		<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="avatar">
 						<head-image :url="user.headImage"></head-image>

+ 18 - 11
im-ui/src/components/friend/FriendItem.vue

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

+ 182 - 0
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 - 0
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 - 0
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>

+ 1 - 1
im-ui/src/components/setting/Setting.vue

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

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

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

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

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

+ 10 - 23
im-ui/src/view/Chat.vue

@@ -1,18 +1,14 @@
 <template>
 	<el-container>
 		<el-aside width="250px" class="l-chat-box">
-			<el-header height="60px">
-				<el-row>
-					<el-input width="200px" placeholder="搜索" v-model="searchText">
-						<el-button slot="append" icon="el-icon-search"></el-button>
-					</el-input>
-				</el-row>
-			</el-header>
-			<el-main>
-				<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>
+			<div class="l-chat-header">
+				<el-input width="200px" placeholder="搜索" v-model="searchText">
+					<el-button slot="append" icon="el-icon-search"></el-button>
+				</el-input>
+			</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-aside>
 		<el-container class="r-chat-box">
 			<el-header height="60px">
@@ -308,37 +304,28 @@
 
 <style lang="scss">
 	.el-container {
-
 		.l-chat-box {
 			border: #dddddd solid 1px;
-			background: #eeeeee;
+			background: white;
 			width: 3rem;
-
-			.el-header {
+			.l-chat-header {
 				padding: 5px;
 				background-color: white;
 				line-height: 50px;
 			}
-
-			.el-main {
-				padding: 0
-			}
 		}
 
 		.r-chat-box {
 			background: white;
 			border: #dddddd solid 1px;
-
 			.el-header {
 				padding: 5px;
 				background-color: white;
 				line-height: 50px;
 			}
-
 			.im-chat-main {
 				padding: 0;
 				border: #dddddd solid 1px;
-
 				.im-chat-box {
 					ul {
 						padding: 20px;

+ 15 - 33
im-ui/src/view/Friend.vue

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

+ 216 - 18
im-ui/src/view/Group.vue

@@ -1,7 +1,7 @@
 <template>
 	<el-container class="im-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">
 					<el-input width="200px" placeholder="搜索群聊" v-model="searchText">
 						<el-button slot="append" icon="el-icon-search"></el-button>
@@ -9,19 +9,68 @@
 				</div>
 				<el-button plain icon="el-icon-plus" style="border: none; padding: 12px; font-size: 20px;color: black;" title="创建群聊"
 				 @click="handleCreateGroup()"></el-button>
-			</el-header>
-			<el-main>
-				<el-main>
-					<div v-for="(group,index) in groupStore.groups" :key="group.id">
-						<group-item v-show="group.name.startsWith(searchText)" :group="group" :index="index" :active="index === groupStore.activeIndex"
-						 @click.native="handleActiveItem(group,index)">
-						</group-item>
-					</div>
-				</el-main>
-			</el-main>
+			</div>
+
+			<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"
+				 @click.native="handleActiveItem(group,index)">
+				</group-item>
+			</div>
 		</el-aside>
-		<el-container class="r-chat-box">
-			group
+		<el-container class="r-group-box">
+			<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>
 </template>
@@ -29,15 +78,24 @@
 
 <script>
 	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 {
 		name: "group",
 		components: {
-			GroupItem
+			GroupItem,
+			GroupMember,
+			FileUpload,
+			AddGroupMember
 		},
 		data() {
 			return {
-				searchText: ""
+				searchText: "",
+				maxSize: 5 * 1024 * 1024,
+				groupMembers:[],
+				showAddGroupMember: false
 			};
 		},
 		methods: {
@@ -58,11 +116,47 @@
 			},
 			handleActiveItem(group, 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: {
 			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 {
 		.l-group-box {
 			border: #dddddd solid 1px;
-			background: #eeeeee;
+			background: white;
 
 			.l-group-header {
+				height: 50px;
 				display: flex;
 				align-items: center;
 				padding: 5px;
 				background-color: white;
-
 				.l-group-search {
 					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>