Browse Source

支持发送图片

master
xie.bx 3 years ago
parent
commit
cd2b3ef146
  1. 13
      im-ui/src/api/wssocket.js
  2. BIN
      im-ui/src/assets/default_head.png
  3. 35
      im-ui/src/components/HeadImage.vue
  4. 330
      im-ui/src/components/chat/ChatItem.vue
  5. 84
      im-ui/src/components/chat/ChatTime.vue
  6. 209
      im-ui/src/components/chat/MessageItem.vue
  7. 87
      im-ui/src/components/common/FileUpload.vue
  8. 54
      im-ui/src/components/common/HeadImage.vue
  9. 238
      im-ui/src/components/friend/AddFriends.vue
  10. 228
      im-ui/src/components/friend/FriendsItem.vue
  11. 55
      im-ui/src/components/setting/Setting.vue
  12. 13
      im-ui/src/store/chatStore.js
  13. 363
      im-ui/src/view/Chat.vue
  14. 10
      im-ui/src/view/Friends.vue
  15. 2
      im-ui/src/view/Home.vue

13
im-ui/src/api/wssocket.js

@ -20,7 +20,7 @@ let initWebSocket = () => {
isCompleteConnect = false;
websock = new WebSocket(wsurl);
websock.onmessage = function(e) {
let msg = JSON.parse(decodeUnicode(e.data))
let msg = JSON.parse(e.data)
if (msg.cmd == 0) {
if(!isCompleteConnect){
// 第一次上传心跳成功才算连接完成
@ -131,17 +131,6 @@ function onopen(callback) {
}
function decodeUnicode(str) {
str = str.replace(/\\/g, "%");
//转换中文
str = unescape(str);
//将其他受影响的转换回原来
str = str.replace(/%/g, "\\");
//对网址的链接进行处理
str = str.replace(/\\/g, "");
return str;
}
// 将方法暴露出去

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 133 KiB

After

Width:  |  Height:  |  Size: 32 KiB

35
im-ui/src/components/HeadImage.vue

@ -1,35 +0,0 @@
<template>
<div class='img-box'>
<img :src="url" style="width: 100%;height: 100%;cursor: pointer;" />
</div>
</template>
<script>
export default {
name: "headImage",
data() {
return {}
},
props: {
size: {
type: Number,
default: 50
},
url:{
type: String,
default: '../assets/default_head.png'
}
}
}
</script>
<style scoped>
.img-box {
width: 100%;
height: 100%;
display: inline-block;
border-radius: 3px;
background-color: #c0c4cc;
overflow: hidden;
}
</style>

330
im-ui/src/components/ChatItem.vue → im-ui/src/components/chat/ChatItem.vue

@ -1,165 +1,165 @@
<template>
<div class="item" :class="active ? 'active' : ''">
<div class="left">
<head-image :url="chat.headImage" :size="40">
</head-image>
<div v-show="chat.unreadCount>0" class="unread-text">{{chat.unreadCount}}</div>
</div>
<div class="mid">
<div>{{ chat.showName}}</div>
<div class="msg-text">{{chat.lastContent}}</div>
</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>
</div>
</div>
</template>
<script>
import ChatTime from "./ChatTime.vue";
import HeadImage from './HeadImage.vue';
export default {
name: "chatItem",
components: {
ChatTime,
HeadImage
},
data() {
return {}
},
props: {
chat: {
type: Object
},
active: {
type: Boolean
},
index: {
type: Number
}
},
methods: {
onClickClose(){
this.$emit("del");
}
}
}
</script>
<style scode lang="scss">
.item {
height: 65px;
display: flex;
margin-bottom: 1px;
position: relative;
padding-left: 15px;
align-items: center;
padding-right: 5px;
background-color: #eeeeee;
&:hover {
background-color: #dddddd;
}
&.active {
background-color: #cccccc;
}
&:hover {
.close {
display: block !important;
}
}
.left {
position: relative;
display: flex;
width: 45px;
height: 45px;
.unread-text {
position: absolute;
background-color: #f56c6c;
right: -8px;
top: -8px;
color: white;
border-radius: 30px;
padding: 0 5px;
font-size: 10px;
text-align: center;
white-space: nowrap;
border: 1px solid #f1e5e5;
}
}
.mid {
margin-left: 15px;
flex: 3;
display: flex;
flex-direction: column;
height: 100%;
flex-shrink: 0;
overflow: hidden;
&>div {
display: flex;
justify-content: flex-start;
align-items: center;
flex: 1;
}
.msg-text {
font-size: 14px;
color: #888888;
white-space: nowrap;
}
}
.right {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-end;
height: 100%;
flex-shrink: 0;
overflow: hidden;
&>div {
display: flex;
justify-content: flex-start;
align-items: center;
flex: 1;
}
.close {
width: 1.5rem;
height: 1.5rem;
right: 0;
top: 1rem;
cursor: pointer;
display: none;
}
.msg-time {
font-size: 14px;
color: #888888;
white-space: nowrap;
}
}
}
.active {
background-color: #eeeeee;
}
</style>
<template>
<div class="item" :class="active ? 'active' : ''">
<div class="left">
<head-image :url="chat.headImage" :size="40">
</head-image>
<div v-show="chat.unreadCount>0" class="unread-text">{{chat.unreadCount}}</div>
</div>
<div class="mid">
<div>{{ chat.showName}}</div>
<div class="msg-text">{{chat.lastContent}}</div>
</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>
</div>
</div>
</template>
<script>
import ChatTime from "./ChatTime.vue";
import HeadImage from '../common/HeadImage.vue';
export default {
name: "chatItem",
components: {
ChatTime,
HeadImage
},
data() {
return {}
},
props: {
chat: {
type: Object
},
active: {
type: Boolean
},
index: {
type: Number
}
},
methods: {
onClickClose(){
this.$emit("del");
}
}
}
</script>
<style scode lang="scss">
.item {
height: 65px;
display: flex;
margin-bottom: 1px;
position: relative;
padding-left: 15px;
align-items: center;
padding-right: 5px;
background-color: #eeeeee;
&:hover {
background-color: #dddddd;
}
&.active {
background-color: #cccccc;
}
&:hover {
.close {
display: block !important;
}
}
.left {
position: relative;
display: flex;
width: 45px;
height: 45px;
.unread-text {
position: absolute;
background-color: #f56c6c;
right: -8px;
top: -8px;
color: white;
border-radius: 30px;
padding: 0 5px;
font-size: 10px;
text-align: center;
white-space: nowrap;
border: 1px solid #f1e5e5;
}
}
.mid {
margin-left: 15px;
flex: 3;
display: flex;
flex-direction: column;
height: 100%;
flex-shrink: 0;
overflow: hidden;
&>div {
display: flex;
justify-content: flex-start;
align-items: center;
flex: 1;
}
.msg-text {
font-size: 14px;
color: #888888;
white-space: nowrap;
}
}
.right {
flex: 1;
display: flex;
flex-direction: column;
align-items: flex-end;
height: 100%;
flex-shrink: 0;
overflow: hidden;
&>div {
display: flex;
justify-content: flex-start;
align-items: center;
flex: 1;
}
.close {
width: 1.5rem;
height: 1.5rem;
right: 0;
top: 1rem;
cursor: pointer;
display: none;
}
.msg-time {
font-size: 14px;
color: #888888;
white-space: nowrap;
}
}
}
.active {
background-color: #eeeeee;
}
</style>

84
im-ui/src/components/ChatTime.vue → im-ui/src/components/chat/ChatTime.vue

@ -1,43 +1,41 @@
<template>
<span>{{formatDate}}</span>
</template>
<script>
export default {
name: "chatTime",
data() {
return {}
},
props: {
time: {
type: Number
}
},
computed:{
formatDate(){
console.log(this.time);
let time = new Date(this.time);
let strtime = "";
let curTime = new Date();
let dayDiff =curTime.getDate() - time.getDate() ;
if (time.getDate() === new Date().getDate()) {
strtime = time.getHours() <= 9 ? "0" + time.getHours() : time.getHours();
strtime += ":"
strtime += time.getMinutes() <= 9 ? "0" + time.getMinutes() : time.getMinutes();
} else if (dayDiff === 1) {
strtime = "昨天";
} else if (dayDiff < 7) {
strtime = `${dayDiff}天前`;
} else {
strtime = time.getMonth()+1+"月"+time.getDate()+"日";
}
console.log(strtime);
return strtime;
}
}
}
</script>
<style>
</style>
<template>
<span>{{formatDate}}</span>
</template>
<script>
export default {
name: "chatTime",
data() {
return {}
},
props: {
time: {
type: Number
}
},
computed:{
formatDate(){
let time = new Date(this.time);
let strtime = "";
let curTime = new Date();
let dayDiff =curTime.getDate() - time.getDate() ;
if (time.getDate() === new Date().getDate()) {
strtime = time.getHours() <= 9 ? "0" + time.getHours() : time.getHours();
strtime += ":"
strtime += time.getMinutes() <= 9 ? "0" + time.getMinutes() : time.getMinutes();
} else if (dayDiff === 1) {
strtime = "昨天";
} else if (dayDiff < 7) {
strtime = `${dayDiff}天前`;
} else {
strtime = time.getMonth()+1+"月"+time.getDate()+"日";
}
return strtime;
}
}
}
</script>
<style>
</style>

209
im-ui/src/components/chat/MessageItem.vue

@ -0,0 +1,209 @@
<template>
<div class="im-msg-item" :class="{'im-chat-mine':mine}">
<div class="head-image">
<head-image :url="headImage"></head-image>
</div>
<div class="im-msg-content">
<div class="im-msg-top">
<span>{{showName}}</span>
<chat-time :time="msgInfo.sendTime"></chat-time>
</div>
<div class="im-msg-bottom">
<span class="im-msg-text" v-if="msgInfo.type==0" >{{msgInfo.content}}</span>
<div class="im-msg-image" v-if="msgInfo.type==1">
<div class="img-load-box" v-loading="loading"
element-loading-text="上传中.."
element-loading-background="rgba(0, 0, 0, 0.4)">
<img class="send-image" :src="JSON.parse(msgInfo.content).thumbUrl"/>
</div>
<span title="发送失败" v-show="loadFail" @click="handleSendFail" class="send-fail el-icon-warning"></span>
</div>
</div>
</div>
</div>
</template>
<script>
import ChatTime from "./ChatTime.vue";
import HeadImage from "../common/HeadImage.vue";
export default {
name: "messageItem",
components: {
ChatTime,
HeadImage
},
props: {
mine:{
type:Boolean,
required: true
},
headImage: {
type: String,
required: true
},
showName: {
type: String,
required: true
},
msgInfo: {
type: Object,
required: true
}
},
methods:{
handleSendFail(){
this.$message.error("该文件已上传失败,目前不支持自动重新发送,建议手动重新发送")
}
},
computed:{
loading(){
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "loadding";
},
loadFail(){
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "fail";
}
}
}
</script>
<style lang="scss">
.im-msg-item {
position: relative;
font-size: 0;
margin-bottom: 10px;
padding-left: 60px;
min-height: 68px;
.head-image {
position: absolute;
width: 40px;
height: 40px;
top: 0;
left: 0;
}
.im-msg-content {
display: flex;
flex-direction: column;
.im-msg-top {
display: flex;
flex-wrap: nowrap;
color: #333;
font-size: 14px;
line-height: 20px;
span {
margin-right: 12px;
}
}
.im-msg-bottom {
text-align: left;
.im-msg-text {
position: relative;
line-height: 22px;
margin-top: 10px;
padding: 10px;
background-color: #eeeeee;
border-radius: 3px;
color: #333;
display: inline-block;
font-size: 14px;
&:after {
content: "";
position: absolute;
left: -10px;
top: 13px;
width: 0;
height: 0;
border-style: solid dashed dashed;
border-color: #eeeeee transparent transparent;
overflow: hidden;
border-width: 10px;
}
}
.im-msg-image{
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
.send-image{
min-width: 300px;
min-height: 200px;
max-width: 600px;
max-height: 400px;
border: #dddddd solid 1px;
cursor: pointer;
}
.send-fail{
color: red;
font-size: 30px;
cursor: pointer;
margin: 0 20px;
}
}
}
}
&.im-chat-mine {
text-align: right;
padding-left: 0;
padding-right: 60px;
.head-image {
left: auto;
right: 0;
}
.im-msg-content {
.im-msg-top {
flex-direction: row-reverse;
span {
margin-left: 12px;
margin-right: 0;
}
}
.im-msg-bottom {
text-align: right;
.im-msg-text {
margin-left: 10px;
background-color: #5fb878;
color: #fff;
display: inline-block;
vertical-align: top;
font-size: 14px;
&:after {
left: auto;
right: -10px;
border-top-color: #5fb878;
}
}
.im-msg-image {
flex-direction: row-reverse;
}
}
}
.message-info {
right: 60px !important;
display: inline-block;
}
}
}
</style>

87
im-ui/src/components/common/FileUpload.vue

@ -0,0 +1,87 @@
<template>
<el-upload :action="action" :accept="fileTypes.join(',')" :show-file-list="false" :on-success="handleSuccess"
:before-upload="beforeUpload">
<slot></slot>
</el-upload>
</template>
<script>
export default {
name: "fileUpload",
data() {
return {
loading: null
}
},
props: {
action: {
type: String,
required: true
},
fileTypes: {
type: Array,
default: null
},
maxSize: {
type: Number,
default: null
},
showLoading: {
type: Boolean,
default: false
}
},
methods: {
handleSuccess(res, file) {
this.loading && this.loading.close();
if (res.code == 200) {
this.$emit("success", res, file);
}else{
this.$message.error(res.message);
this.$emit("fail", res, file);
}
},
beforeUpload(file) {
//
let fileType = file.type;
let t = this.fileTypes.find((t) => t.toLowerCase() === fileType);
console.log(t);
if (t === undefined) {
this.$message.error(`文件格式错误,请上传以下格式的文件:${this.fileTypes.join("、")}`);
return false;
}
//
let size = file.size;
if (size > this.maxSize) {
this.$message.error(`文件大小不能超过 ${this.fileSizeStr}!`);
return false;
}
if (this.showLoading) {
this.loading = this.$loading({
lock: true,
text: '正在上传...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
}
this.$emit("before", file);
return true;
}
},
computed: {
fileSizeStr() {
if (this.maxSize > 1024 * 1024) {
return (this.maxSize / 1024 / 1024) + "M";
}
if (this.maxSize > 1024) {
return (this.maxSize / 1024) + "KB";
}
return this.maxSize + "B";
}
}
}
</script>
<style>
</style>

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

@ -0,0 +1,54 @@
<template>
<div class='img-box'>
<img :src="url"
style="width: 100%;height: 100%;cursor: pointer;" />
</div>
</template>
<script>
export default {
name: "headImage",
data() {
return {
}
},
methods:{
},
props: {
size: {
type: Number,
default: 50
},
url:{
type: String
}
}
}
</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;
}
img:before {
content: '';
display: block;
width: 100%;
height: 100%;
background:url('../../assets/default_head.png') no-repeat 0 0;
background-size: 100%;
}
}
</style>

238
im-ui/src/components/AddFriends.vue → im-ui/src/components/friend/AddFriends.vue

@ -1,119 +1,119 @@
<template>
<el-dialog title="添加好友" :visible.sync="dialogVisible" width="350px" :before-close="onClose">
<el-input width="200px" placeholder="搜索好友" class="input-with-select" v-model="searchText" @keyup.enter.native="onSearch()">
<el-button slot="append" icon="el-icon-search" @click="onSearch()"></el-button>
</el-input>
<el-scrollbar style="height:400px">
<div v-for="(userInfo) in users" :key="userInfo.id">
<div class="item">
<div class="avatar">
<head-image :url="userInfo.headImage"></head-image>
</div>
<div class="add-friend-text">
<div>{{userInfo.nickName}}</div>
<div :class="userInfo.online ? 'online-status online':'online-status'">{{ userInfo.online?"[在线]":"[离线]"}}</div>
</div>
<el-button type="success" v-show="!isFriend(userInfo.id)" plain @click="onAddFriends(userInfo)">添加</el-button>
<el-button type="info" v-show="isFriend(userInfo.id)" plain disabled>已添加</el-button>
</div>
</div>
</el-scrollbar>
</el-dialog>
</template>
<script>
import HeadImage from './HeadImage.vue'
export default {
name: "addFriends",
components:{HeadImage},
data() {
return {
users: [],
searchText: ""
}
},
props: {
dialogVisible: {
type: Boolean
}
},
methods: {
onClose() {
this.$emit("close");
},
onSearch() {
this.$http({
url: "/api/user/findByNickName",
method: "get",
params: {
nickName: this.searchText
}
}).then((data) => {
this.users = data;
})
},
onAddFriends(userInfo){
this.$http({
url: "/api/friends/add",
method: "post",
params: {
friendId: userInfo.id
}
}).then((data) => {
this.$store.commit("")
this.$message.success("添加成功,对方已成为您的好友");
let friendsInfo = {
friendId:userInfo.id,
friendNickName: userInfo.nickName,
friendHeadImage: userInfo.headImage,
online: userInfo.online
}
this.$store.commit("addFriends",friendsInfo);
})
},
isFriend(userId){
let friendList = this.$store.state.friendsStore.friendsList;
let friend = friendList.find((f)=> f.friendId==userId);
return friend != undefined;
}
},
mounted() {
this.onSearch();
}
}
</script>
<style scoped lang="scss">
.item {
height: 80px;
display: flex;
position: relative;
padding-left: 15px;
align-items: center;
padding-right: 25px;
.add-friend-text {
margin-left: 15px;
line-height: 80px;
flex: 3;
display: flex;
flex-direction: row;
height: 100%;
flex-shrink: 0;
overflow: hidden;
.online-status{
font-size: 12px;
font-weight: 600;
&.online{
color: #5fb878;
}
}
}
}
</style>
<template>
<el-dialog title="添加好友" :visible.sync="dialogVisible" width="350px" :before-close="onClose">
<el-input width="200px" placeholder="搜索好友" class="input-with-select" v-model="searchText" @keyup.enter.native="onSearch()">
<el-button slot="append" icon="el-icon-search" @click="onSearch()"></el-button>
</el-input>
<el-scrollbar style="height:400px">
<div v-for="(userInfo) in users" :key="userInfo.id">
<div class="item">
<div class="avatar">
<head-image :url="userInfo.headImage"></head-image>
</div>
<div class="add-friend-text">
<div>{{userInfo.nickName}}</div>
<div :class="userInfo.online ? 'online-status online':'online-status'">{{ userInfo.online?"[在线]":"[离线]"}}</div>
</div>
<el-button type="success" v-show="!isFriend(userInfo.id)" plain @click="onAddFriends(userInfo)">添加</el-button>
<el-button type="info" v-show="isFriend(userInfo.id)" plain disabled>已添加</el-button>
</div>
</div>
</el-scrollbar>
</el-dialog>
</template>
<script>
import HeadImage from '../common/HeadImage.vue'
export default {
name: "addFriends",
components:{HeadImage},
data() {
return {
users: [],
searchText: ""
}
},
props: {
dialogVisible: {
type: Boolean
}
},
methods: {
onClose() {
this.$emit("close");
},
onSearch() {
this.$http({
url: "/api/user/findByNickName",
method: "get",
params: {
nickName: this.searchText
}
}).then((data) => {
this.users = data;
})
},
onAddFriends(userInfo){
this.$http({
url: "/api/friends/add",
method: "post",
params: {
friendId: userInfo.id
}
}).then((data) => {
this.$store.commit("")
this.$message.success("添加成功,对方已成为您的好友");
let friendsInfo = {
friendId:userInfo.id,
friendNickName: userInfo.nickName,
friendHeadImage: userInfo.headImage,
online: userInfo.online
}
this.$store.commit("addFriends",friendsInfo);
})
},
isFriend(userId){
let friendList = this.$store.state.friendsStore.friendsList;
let friend = friendList.find((f)=> f.friendId==userId);
return friend != undefined;
}
},
mounted() {
this.onSearch();
}
}
</script>
<style scoped lang="scss">
.item {
height: 80px;
display: flex;
position: relative;
padding-left: 15px;
align-items: center;
padding-right: 25px;
.add-friend-text {
margin-left: 15px;
line-height: 80px;
flex: 3;
display: flex;
flex-direction: row;
height: 100%;
flex-shrink: 0;
overflow: hidden;
.online-status{
font-size: 12px;
font-weight: 600;
&.online{
color: #5fb878;
}
}
}
}
</style>

228
im-ui/src/components/FriendsItem.vue → im-ui/src/components/friend/FriendsItem.vue

@ -1,114 +1,114 @@
<template>
<div class="item" :class="active ? 'active' : ''">
<div class="avatar">
<head-image :url="friendsInfo.friendHeadImage" > </head-image>
</div>
<div class="text">
<div>{{ friendsInfo.friendNickName}}</div>
<div :class="online ? 'online-status online':'online-status'">{{ online?"[在线]":"[离线]"}}</div>
</div>
<div class="close" @click.stop="$emit('del',friendsInfo,index)">
<i class="el-icon-close" style="border: none; font-size: 20px;color: black;" title="添加好友"></i>
</div>
</div>
</template>
<script>
import HeadImage from './HeadImage.vue';
export default {
name: "frinedsItem",
components: {HeadImage},
data() {
return {
}
},
props: {
friendsInfo: {
type: Object
},
active:{
type: Boolean
},
index:{
type: Number
}
},
computed:{
online(){
return this.$store.state.friendsStore.friendsList[this.index].online;
}
}
}
</script>
<style scope lang="scss">
.item {
height: 65px;
display: flex;
margin-bottom: 1px;
position: relative;
padding-left: 15px;
align-items: center;
padding-right: 5px;
background-color: #eeeeee;
&:hover {
background-color: #dddddd;
}
&.active{
background-color: #cccccc;
}
.close {
width: 1.5rem;
height: 1.5rem;
right: 10px;
top: 1.825rem;
cursor: pointer;
display: none;
}
&:hover {
.close {
display: block;
}
}
.avatar {
display: flex;
justify-content: center;
align-items: center;
width: 45px;
height: 45px;
}
.text {
margin-left: 15px;
flex: 3;
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
flex-shrink: 0;
overflow: hidden;
&>div {
display: flex;
justify-content: flex-start;
}
.online-status{
font-size: 12px;
font-weight: 600;
&.online{
color: #5fb878;
}
}
}
}
.active {
background-color: #eeeeee;
}
</style>
<template>
<div class="item" :class="active ? 'active' : ''">
<div class="avatar">
<head-image :url="friendsInfo.friendHeadImage" > </head-image>
</div>
<div class="text">
<div>{{ friendsInfo.friendNickName}}</div>
<div :class="online ? 'online-status online':'online-status'">{{ online?"[在线]":"[离线]"}}</div>
</div>
<div class="close" @click.stop="$emit('del',friendsInfo,index)">
<i class="el-icon-close" style="border: none; font-size: 20px;color: black;" title="添加好友"></i>
</div>
</div>
</template>
<script>
import HeadImage from '../common/HeadImage.vue';
export default {
name: "frinedsItem",
components: {HeadImage},
data() {
return {
}
},
props: {
friendsInfo: {
type: Object
},
active:{
type: Boolean
},
index:{
type: Number
}
},
computed:{
online(){
return this.$store.state.friendsStore.friendsList[this.index].online;
}
}
}
</script>
<style scope lang="scss">
.item {
height: 65px;
display: flex;
margin-bottom: 1px;
position: relative;
padding-left: 15px;
align-items: center;
padding-right: 5px;
background-color: #eeeeee;
&:hover {
background-color: #dddddd;
}
&.active{
background-color: #cccccc;
}
.close {
width: 1.5rem;
height: 1.5rem;
right: 10px;
top: 1.825rem;
cursor: pointer;
display: none;
}
&:hover {
.close {
display: block;
}
}
.avatar {
display: flex;
justify-content: center;
align-items: center;
width: 45px;
height: 45px;
}
.text {
margin-left: 15px;
flex: 3;
display: flex;
flex-direction: column;
justify-content: space-around;
height: 100%;
flex-shrink: 0;
overflow: hidden;
&>div {
display: flex;
justify-content: flex-start;
}
.online-status{
font-size: 12px;
font-weight: 600;
&.online{
color: #5fb878;
}
}
}
}
.active {
background-color: #eeeeee;
}
</style>

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

@ -2,11 +2,16 @@
<el-dialog class="setting" title="设置" :visible.sync="visible" width="30%" :before-close="handleClose">
<el-form :model="userInfo" label-width="80px" :rules="rules" ref="settingForm">
<el-form-item label="头像">
<el-upload class="avatar-uploader" action="/api/image/upload" :show-file-list="false" :on-success="handleAvatarSuccess"
:before-upload="beforeAvatarUpload" accept="image/jpeg, image/png, image/jpg">
<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="userInfo.headImage" :src="userInfo.headImage" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</file-upload>
</el-form-item>
<el-form-item label="用户名">
<el-input disabled v-model="userInfo.userName" autocomplete="off"></el-input>
@ -33,18 +38,22 @@
</template>
<script>
import HeadImage from "../HeadImage.vue";
import HeadImage from "../common/HeadImage.vue";
import FileUpload from "../common/FileUpload.vue";
export default {
name: "setting",
components: {
HeadImage
HeadImage,
FileUpload
},
data() {
return {
userInfo: {
},
maxSize: 5*1024*1024,
action: "/api/image/upload",
rules: {
nickName: [{
required: true,
@ -57,7 +66,6 @@
methods: {
handleClose() {
this.visible = false;
this.$emit("close");
},
handleSubmit() {
@ -71,39 +79,14 @@
data: this.userInfo
}).then(()=>{
this.$store.commit("setUserInfo",this.userInfo);
this.visible = false;
this.$emit("close");
this.$message.success("修改成功");
})
});
},
handleAvatarSuccess(res, file) {
console.log(res);
this.loading.close();
if (res.code == 200) {
this.userInfo.headImage = res.data.originUrl;
this.userInfo.headImageThumb = res.data.thumbUrl;
} else {
this.$message.error(res.message);
}
},
beforeAvatarUpload(file) {
const limitSize = file.size * 1024 * 1024;
if (file.size > limitSize) {
this.$message.error('上传头像图片大小不能超过 5MB!');
return false
}
this.loading = this.$loading({
lock: true,
text: '正在上传...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
return true;
handleUploadSuccess(res, file) {
this.userInfo.headImage = res.data.originUrl;
this.userInfo.headImageThumb = res.data.thumbUrl;
}
},
props: {
@ -119,7 +102,7 @@
}
</script>
<style lang="scss">
<style lang="scss" >
.setting {
.avatar-uploader {

13
im-ui/src/store/chatStore.js

@ -60,9 +60,18 @@ export default {
chat.unreadCount++;
}
},
handleFileUpload(state,info){
// 文件上传后数据更新
let chat = state.chats.find((c)=>c.targetId === info.targetId);
if(chat){
let msg = chat.messages.find((m)=>info.fileId==m.fileId);
msg.loadStatus = info.loadStatus;
if(info.content){
msg.content = info.content;
}
}
},
setChatUserInfo(state, userInfo){
console.log(userInfo)
for(let i in state.chats){
if(state.chats[i].targetId == userInfo.id){
state.chats[i].headImage = userInfo.headImageThumb;

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

@ -9,42 +9,46 @@
</el-row>
</el-header>
<el-main>
<div v-for="(chat,index) in $store.state.chatStore.chats" :key="chat.targetId">
<chat-item :chat="chat" :index="index" @click.native="onClickItem(index)" @del="onDelItem(chat,index)"
:active="index === $store.state.chatStore.activeIndex"></chat-item>
<div v-for="(chat,index) in chatStore.chats" :key="chat.targetId">
<chat-item :chat="chat" :index="index" @click.native="onClickItem(index)" @del="onDelItem(chat,index)" :active="index === chatStore.activeIndex"></chat-item>
</div>
</el-main>
</el-aside>
<el-container class="r-chat-box">
<el-header height="60px">
{{titleName}}
{{activeChat.showName}}
</el-header>
<el-main class="im-chat-main" id="chatScrollBox">
<div class="im-chat-box">
<ul>
<li v-for="item in messages" :key="item.id"
:class="{ 'im-chat-mine': item.sendUserId == $store.state.userStore.userInfo.id }">
<div class="head-image">
<head-image :url="headImage(item)" ></head-image>
</div>
<div class="im-msg-content">
<div class="im-msg-top">
<span>{{showName(item)}}</span>
<chat-time :time="item.sendTime"></chat-time>
</div>
<div class="im-msg-bottom">
<span class="im-msg-text">{{item.content}}</span>
</div>
</div>
</li>
</ul>
<div class="im-chat-box">
<ul>
<li v-for="msgInfo in activeChat.messages" :key="msgInfo.id">
<message-item :mine="msgInfo.sendUserId == $store.state.userStore.userInfo.id"
:headImage="headImage(msgInfo)"
:showName="showName(msgInfo)" :msgInfo="msgInfo">
</message-item>
</li>
</ul>
</div>
</el-main>
<el-footer height="25%" class="im-chat-footer">
<div class="chat-tool-bar"></div>
<textarea v-model="messageContent" ref="sendBox" class="textarea" @keyup.enter="onSendMessage()"></textarea>
<div class="chat-tool-bar">
<div class="el-icon-service"></div>
<div>
<file-upload action="/api/image/upload"
:maxSize="5*1024*1024"
:fileTypes="['image/jpeg', 'image/png', 'image/jpg', 'image/gif']"
@before="beforeImageUpload"
@success="handleImageSuccess"
@fail="handleImageFail" >
<i class="el-icon-picture-outline"></i>
</file-upload>
</div>
<div class="el-icon-wallet"></div>
<div class="el-icon-chat-dot-round"></div>
</div>
<textarea v-model="messageContent" ref="sendBox" class="send-text-area" @keyup.enter="onSendMessage()"></textarea>
<div class="im-chat-send">
<el-button type="primary" @click="onSendMessage()">发送</el-button>
</div>
</el-footer>
@ -53,16 +57,20 @@
</template>
<script>
import ChatItem from "../components/ChatItem.vue";
import ChatTime from "../components/ChatTime.vue";
import HeadImage from "../components/HeadImage.vue";
import ChatItem from "../components/chat/ChatItem.vue";
import ChatTime from "../components/chat/ChatTime.vue";
import MessageItem from "../components/chat/MessageItem.vue";
import HeadImage from "../components/common/HeadImage.vue";
import FileUpload from "../components/common/FileUpload.vue";
export default {
name: "chat",
components: {
ChatItem,
ChatTime,
HeadImage
HeadImage,
FileUpload,
MessageItem
},
data() {
return {
@ -83,16 +91,76 @@
let chat = this.chatStore.chats[index];
if (userInfo.headImageThumb != chat.headImage ||
userInfo.nickName != chat.showName) {
this.updateFriendInfo(userInfo,index)
this.updateFriendInfo(userInfo, index)
}
})
},
onSendMessage() {
let msgInfo = {
recvUserId: this.$store.state.chatStore.chats[this.$store.state.chatStore.activeIndex].targetId,
recvUserId: this.activeChat.targetId,
content: this.messageContent,
type: 0
}
this.sendMessage(msgInfo);
},
onDelItem(chat, index) {
this.$store.commit("removeChat", index);
},
handleImageSuccess(res, file) {
let msgInfo = {
recvUserId: file.raw.targetId,
content: JSON.stringify(res.data),
type: 1
}
this.$http({
url: '/api/message/single/send',
method: 'post',
data: msgInfo
}).then((data) => {
let info = {
targetId : file.raw.targetId,
fileId: file.raw.uid,
content: JSON.stringify(res.data),
loadStatus: "ok"
}
this.$store.commit("handleFileUpload", info);
})
},
handleImageFail(res,file){
let info = {
targetId : file.raw.targetId,
fileId: file.raw.uid,
loadStatus: "fail"
}
this.$store.commit("handleFileUpload", info);
},
beforeImageUpload(file) {
console.log(file);
let url = URL.createObjectURL(file);
let data = {
originUrl : url,
thumbUrl: url
}
let msgInfo = {
fileId: file.uid,
sendUserId: this.$store.state.userStore.userInfo.id,
recvUserId: this.activeChat.targetId,
content: JSON.stringify(data),
sendTime: new Date().getTime(),
selfSend: true,
type: 1,
loadStatus: "loadding"
}
//
this.$store.commit("insertMessage", msgInfo);
//
this.scrollToBottom();
// fileid
file.targetId = this.activeChat.targetId;
},
sendMessage(msgInfo) {
this.$http({
url: '/api/message/single/send',
method: 'post',
@ -103,25 +171,16 @@
msgInfo.sendTime = new Date().getTime();
msgInfo.sendUserId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true;
console.log(msgInfo);
msgInfo.loadStatus = "ok";
this.$store.commit("insertMessage", msgInfo);
//
this.$refs.sendBox.focus();
//
this.$nextTick(() => {
const div = document.getElementById("chatScrollBox");
div.scrollTop = div.scrollHeight;
});
this.scrollToBottom();
})
},
onDelItem(chat,index){
this.$store.commit("removeChat",index);
},
updateFriendInfo(userInfo,index){
let friendsInfo={
updateFriendInfo(userInfo, index) {
let friendsInfo = {
friendId: userInfo.id,
friendNickName: userInfo.nickName,
friendHeadImage: userInfo.headImageThumb
@ -131,73 +190,70 @@
method: "put",
data: friendsInfo
}).then(() => {
this.$store.commit("updateFriends",friendsInfo);
this.$store.commit("setChatUserInfo",userInfo);
this.$store.commit("updateFriends", friendsInfo);
this.$store.commit("setChatUserInfo", userInfo);
})
},
showName(msg) {
if (msg.sendUserId == this.$store.state.userStore.userInfo.id) {
return this.$store.state.userStore.userInfo.nickName;
} else {
let index = this.$store.state.chatStore.activeIndex;
let chats = this.$store.state.chatStore.chats
return chats[index].showName;
}
return this.activeChat.showName;
},
headImage(msg){
if(msg.sendUserId == this.$store.state.userStore.userInfo.id){
headImage(msg) {
if (msg.sendUserId == this.$store.state.userStore.userInfo.id) {
return this.$store.state.userStore.userInfo.headImageThumb;
}else{
let index = this.$store.state.chatStore.activeIndex;
let chats = this.$store.state.chatStore.chats
if(index>=0 && chats.length > 0){
let chats = this.$store.state.chatStore.chats;
return chats[index].headImage;
}
}
return "";
return this.activeChat.headImage;
},
scrollToBottom(){
this.$nextTick(() => {
const div = document.getElementById("chatScrollBox");
div.scrollTop = div.scrollHeight;
});
}
},
computed: {
chatStore(){
chatStore() {
return this.$store.state.chatStore;
},
messages() {
let index = this.$store.state.chatStore.activeIndex;
let chats = this.$store.state.chatStore.chats
activeChat() {
let index = this.chatStore.activeIndex;
let chats = this.chatStore.chats
if (index >= 0 && chats.length > 0) {
console.log(chats[index].messages)
return chats[index].messages;
return chats[index];
}
return [];
return this.emptyChat;
},
titleName(){
let index = this.$store.state.chatStore.activeIndex;
let chats = this.$store.state.chatStore.chats;
if(index>=0 && chats.length > 0){
let chats = this.$store.state.chatStore.chats;
return chats[index].showName;
emptyChat() {
//
return {
targetId: -1,
showName: "",
headImage: "",
messages: []
}
return "";
}
}
}
</script>
<style scoped lang="scss">
<style lang="scss">
.el-container {
.l-chat-box {
border: #dddddd solid 1px;
background: #eeeeee;
width: 3rem;
.el-header {
padding: 5px;
background-color: white;
line-height: 50px;
}
.el-main {
padding: 0
}
@ -206,6 +262,7 @@
.r-chat-box {
background: white;
border: #dddddd solid 1px;
.el-header {
padding: 5px;
background-color: white;
@ -215,136 +272,49 @@
.im-chat-main {
padding: 0;
border: #dddddd solid 1px;
.im-chat-box {
ul {
padding: 10px;
padding: 20px;
li {
position: relative;
font-size: 0;
margin-bottom: 10px;
padding-left: 60px;
min-height: 68px;
.head-image {
position: absolute;
width: 40px;
height: 40px;
top: 0;
left: 0;
}
.im-msg-content {
display: flex;
flex-direction: column;
.im-msg-top {
display: flex;
flex-wrap: nowrap;
color: #333;
font-size: 14px;
line-height: 20px;
span {
margin-right: 12px;
}
}
.im-msg-bottom {
text-align: left;
.im-msg-text {
position: relative;
line-height: 22px;
margin-top: 10px;
padding: 10px;
background-color: #eeeeee;
border-radius: 3px;
color: #333;
display: inline-block;
font-size: 14px;
&:after {
content: "";
position: absolute;
left: -10px;
top: 13px;
width: 0;
height: 0;
border-style: solid dashed dashed;
border-color: #eeeeee transparent transparent;
overflow: hidden;
border-width: 10px;
}
}
}
}
list-style-type:none;
}
}
}
}
.im-chat-mine {
text-align: right;
padding-left: 0;
padding-right: 60px;
.head-image {
left: auto;
right: 0;
}
.im-msg-content {
.im-msg-top {
flex-direction: row-reverse;
span {
margin-left: 12px;
margin-right: 0;
}
}
.im-msg-bottom {
text-align: right;
.im-msg-text {
margin-left: 10px;
background-color: #5fb878;
color: #fff;
display: inline-block;
vertical-align: top;
font-size: 14px;
&:after {
left: auto;
right: -10px;
border-top-color: #5fb878;
}
}
}
}
.message-info {
right: 60px !important;
display: inline-block;
}
}
}
}
.im-chat-footer {
display: flex;
flex-direction: column;
padding: 0;
.chat-tool-bar {
display: flex;
position: relative;
width: 100%;
height: 40px;
text-align: left;
border: #dddddd solid 1px;
>div {
margin-left: 10px;
font-size: 22px;
cursor: pointer;
color: #333333;
line-height: 40px;
&:hover {
color: black;
}
}
}
textarea {
.send-text-area {
box-sizing: border-box;
padding: 5px;
width: 100%;
@ -353,13 +323,12 @@
background-color: #f8f8f8 !important;
outline-color: rgba(83, 160, 231, 0.61);
}
.im-chat-send {
text-align: right;
padding: 7px;
}
}
}
}
}
</style>
</style>

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

@ -34,8 +34,8 @@
</el-descriptions-item>
<el-descriptions-item label="昵称">{{ activeUserInfo.nickName }}
</el-descriptions-item>
<el-descriptions-item label="备注">好基友</el-descriptions-item>
<el-descriptions-item label="签名">世界这么大我想去看看</el-descriptions-item>
<el-descriptions-item label="性别">{{ activeUserInfo.sex==0?"男":"女" }}</el-descriptions-item>
<el-descriptions-item label="签名">{{ activeUserInfo.signature }}</el-descriptions-item>
</el-descriptions>
</div>
</div>
@ -48,9 +48,9 @@
</template>
<script>
import FriendsItem from "../components/FriendsItem.vue";
import AddFriends from "../components/AddFriends.vue";
import HeadImage from "../components/HeadImage.vue";
import FriendsItem from "../components/friend/FriendsItem.vue";
import AddFriends from "../components/friend/AddFriends.vue";
import HeadImage from "../components/common/HeadImage.vue";
export default {
name: "friends",

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

@ -32,7 +32,7 @@
</template>
<script>
import HeadImage from '../components/HeadImage.vue';
import HeadImage from '../components/common/HeadImage.vue';
import Setting from '../components/setting/Setting.vue';
export default {

Loading…
Cancel
Save