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

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

@ -1,43 +1,41 @@
<template> <template>
<span>{{formatDate}}</span> <span>{{formatDate}}</span>
</template> </template>
<script> <script>
export default { export default {
name: "chatTime", name: "chatTime",
data() { data() {
return {} return {}
}, },
props: { props: {
time: { time: {
type: Number type: Number
} }
}, },
computed:{ computed:{
formatDate(){ formatDate(){
console.log(this.time); let time = new Date(this.time);
let time = new Date(this.time); let strtime = "";
let strtime = ""; let curTime = new Date();
let curTime = new Date(); let dayDiff =curTime.getDate() - time.getDate() ;
let dayDiff =curTime.getDate() - time.getDate() ; if (time.getDate() === new Date().getDate()) {
if (time.getDate() === new Date().getDate()) { strtime = time.getHours() <= 9 ? "0" + time.getHours() : time.getHours();
strtime = time.getHours() <= 9 ? "0" + time.getHours() : time.getHours(); strtime += ":"
strtime += ":" strtime += time.getMinutes() <= 9 ? "0" + time.getMinutes() : time.getMinutes();
strtime += time.getMinutes() <= 9 ? "0" + time.getMinutes() : time.getMinutes(); } else if (dayDiff === 1) {
} else if (dayDiff === 1) { strtime = "昨天";
strtime = "昨天"; } else if (dayDiff < 7) {
} else if (dayDiff < 7) { strtime = `${dayDiff}天前`;
strtime = `${dayDiff}天前`; } else {
} else { strtime = time.getMonth()+1+"月"+time.getDate()+"日";
strtime = time.getMonth()+1+"月"+time.getDate()+"日"; }
}
return strtime;
console.log(strtime); }
return strtime; }
} }
} </script>
}
</script> <style>
</style>
<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> <template>
<el-dialog title="添加好友" :visible.sync="dialogVisible" width="350px" :before-close="onClose"> <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-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-button slot="append" icon="el-icon-search" @click="onSearch()"></el-button>
</el-input> </el-input>
<el-scrollbar style="height:400px"> <el-scrollbar style="height:400px">
<div v-for="(userInfo) in users" :key="userInfo.id"> <div v-for="(userInfo) in users" :key="userInfo.id">
<div class="item"> <div class="item">
<div class="avatar"> <div class="avatar">
<head-image :url="userInfo.headImage"></head-image> <head-image :url="userInfo.headImage"></head-image>
</div> </div>
<div class="add-friend-text"> <div class="add-friend-text">
<div>{{userInfo.nickName}}</div> <div>{{userInfo.nickName}}</div>
<div :class="userInfo.online ? 'online-status online':'online-status'">{{ userInfo.online?"[在线]":"[离线]"}}</div> <div :class="userInfo.online ? 'online-status online':'online-status'">{{ userInfo.online?"[在线]":"[离线]"}}</div>
</div> </div>
<el-button type="success" v-show="!isFriend(userInfo.id)" plain @click="onAddFriends(userInfo)">添加</el-button> <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> <el-button type="info" v-show="isFriend(userInfo.id)" plain disabled>已添加</el-button>
</div> </div>
</div> </div>
</el-scrollbar> </el-scrollbar>
</el-dialog> </el-dialog>
</template> </template>
<script> <script>
import HeadImage from './HeadImage.vue' import HeadImage from '../common/HeadImage.vue'
export default { export default {
name: "addFriends", name: "addFriends",
components:{HeadImage}, components:{HeadImage},
data() { data() {
return { return {
users: [], users: [],
searchText: "" searchText: ""
} }
}, },
props: { props: {
dialogVisible: { dialogVisible: {
type: Boolean type: Boolean
} }
}, },
methods: { methods: {
onClose() { onClose() {
this.$emit("close"); this.$emit("close");
}, },
onSearch() { onSearch() {
this.$http({ this.$http({
url: "/api/user/findByNickName", url: "/api/user/findByNickName",
method: "get", method: "get",
params: { params: {
nickName: this.searchText nickName: this.searchText
} }
}).then((data) => { }).then((data) => {
this.users = data; this.users = data;
}) })
}, },
onAddFriends(userInfo){ onAddFriends(userInfo){
this.$http({ this.$http({
url: "/api/friends/add", url: "/api/friends/add",
method: "post", method: "post",
params: { params: {
friendId: userInfo.id friendId: userInfo.id
} }
}).then((data) => { }).then((data) => {
this.$store.commit("") this.$store.commit("")
this.$message.success("添加成功,对方已成为您的好友"); this.$message.success("添加成功,对方已成为您的好友");
let friendsInfo = { let friendsInfo = {
friendId:userInfo.id, friendId:userInfo.id,
friendNickName: userInfo.nickName, friendNickName: userInfo.nickName,
friendHeadImage: userInfo.headImage, friendHeadImage: userInfo.headImage,
online: userInfo.online online: userInfo.online
} }
this.$store.commit("addFriends",friendsInfo); this.$store.commit("addFriends",friendsInfo);
}) })
}, },
isFriend(userId){ isFriend(userId){
let friendList = this.$store.state.friendsStore.friendsList; let friendList = this.$store.state.friendsStore.friendsList;
let friend = friendList.find((f)=> f.friendId==userId); let friend = friendList.find((f)=> f.friendId==userId);
return friend != undefined; return friend != undefined;
} }
}, },
mounted() { mounted() {
this.onSearch(); this.onSearch();
} }
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.item { .item {
height: 80px; height: 80px;
display: flex; display: flex;
position: relative; position: relative;
padding-left: 15px; padding-left: 15px;
align-items: center; align-items: center;
padding-right: 25px; padding-right: 25px;
.add-friend-text { .add-friend-text {
margin-left: 15px; margin-left: 15px;
line-height: 80px; line-height: 80px;
flex: 3; flex: 3;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
height: 100%; height: 100%;
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;
.online-status{ .online-status{
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
&.online{ &.online{
color: #5fb878; color: #5fb878;
} }
} }
} }
} }
</style> </style>

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

@ -1,114 +1,114 @@
<template> <template>
<div class="item" :class="active ? 'active' : ''"> <div class="item" :class="active ? 'active' : ''">
<div class="avatar"> <div class="avatar">
<head-image :url="friendsInfo.friendHeadImage" > </head-image> <head-image :url="friendsInfo.friendHeadImage" > </head-image>
</div> </div>
<div class="text"> <div class="text">
<div>{{ friendsInfo.friendNickName}}</div> <div>{{ friendsInfo.friendNickName}}</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',friendsInfo,index)"> <div class="close" @click.stop="$emit('del',friendsInfo,index)">
<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>
</div> </div>
</template> </template>
<script> <script>
import HeadImage from './HeadImage.vue'; import HeadImage from '../common/HeadImage.vue';
export default { export default {
name: "frinedsItem", name: "frinedsItem",
components: {HeadImage}, components: {HeadImage},
data() { data() {
return { return {
} }
}, },
props: { props: {
friendsInfo: { friendsInfo: {
type: Object type: Object
}, },
active:{ active:{
type: Boolean type: Boolean
}, },
index:{ index:{
type: Number type: Number
} }
}, },
computed:{ computed:{
online(){ online(){
return this.$store.state.friendsStore.friendsList[this.index].online; return this.$store.state.friendsStore.friendsList[this.index].online;
} }
} }
} }
</script> </script>
<style scope lang="scss"> <style scope lang="scss">
.item { .item {
height: 65px; height: 65px;
display: flex; display: flex;
margin-bottom: 1px; margin-bottom: 1px;
position: relative; position: relative;
padding-left: 15px; padding-left: 15px;
align-items: center; align-items: center;
padding-right: 5px; padding-right: 5px;
background-color: #eeeeee; background-color: #eeeeee;
&:hover { &:hover {
background-color: #dddddd; background-color: #dddddd;
} }
&.active{ &.active{
background-color: #cccccc; background-color: #cccccc;
} }
.close { .close {
width: 1.5rem; width: 1.5rem;
height: 1.5rem; height: 1.5rem;
right: 10px; right: 10px;
top: 1.825rem; top: 1.825rem;
cursor: pointer; cursor: pointer;
display: none; display: none;
} }
&:hover { &:hover {
.close { .close {
display: block; display: block;
} }
} }
.avatar { .avatar {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
width: 45px; width: 45px;
height: 45px; height: 45px;
} }
.text { .text {
margin-left: 15px; margin-left: 15px;
flex: 3; flex: 3;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
justify-content: space-around; justify-content: space-around;
height: 100%; height: 100%;
flex-shrink: 0; flex-shrink: 0;
overflow: hidden; overflow: hidden;
&>div { &>div {
display: flex; display: flex;
justify-content: flex-start; justify-content: flex-start;
} }
.online-status{ .online-status{
font-size: 12px; font-size: 12px;
font-weight: 600; font-weight: 600;
&.online{ &.online{
color: #5fb878; color: #5fb878;
} }
} }
} }
} }
.active { .active {
background-color: #eeeeee; background-color: #eeeeee;
} }
</style> </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-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 :model="userInfo" label-width="80px" :rules="rules" ref="settingForm">
<el-form-item label="头像"> <el-form-item label="头像">
<el-upload class="avatar-uploader" action="/api/image/upload" :show-file-list="false" :on-success="handleAvatarSuccess" <file-upload class="avatar-uploader"
:before-upload="beforeAvatarUpload" accept="image/jpeg, image/png, image/jpg"> 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"> <img v-if="userInfo.headImage" :src="userInfo.headImage" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i> <i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload> </file-upload>
</el-form-item> </el-form-item>
<el-form-item label="用户名"> <el-form-item label="用户名">
<el-input disabled v-model="userInfo.userName" autocomplete="off"></el-input> <el-input disabled v-model="userInfo.userName" autocomplete="off"></el-input>
@ -33,18 +38,22 @@
</template> </template>
<script> <script>
import HeadImage from "../HeadImage.vue"; import HeadImage from "../common/HeadImage.vue";
import FileUpload from "../common/FileUpload.vue";
export default { export default {
name: "setting", name: "setting",
components: { components: {
HeadImage HeadImage,
FileUpload
}, },
data() { data() {
return { return {
userInfo: { userInfo: {
}, },
maxSize: 5*1024*1024,
action: "/api/image/upload",
rules: { rules: {
nickName: [{ nickName: [{
required: true, required: true,
@ -57,7 +66,6 @@
methods: { methods: {
handleClose() { handleClose() {
this.visible = false;
this.$emit("close"); this.$emit("close");
}, },
handleSubmit() { handleSubmit() {
@ -71,39 +79,14 @@
data: this.userInfo data: this.userInfo
}).then(()=>{ }).then(()=>{
this.$store.commit("setUserInfo",this.userInfo); this.$store.commit("setUserInfo",this.userInfo);
this.visible = false;
this.$emit("close"); this.$emit("close");
this.$message.success("修改成功"); this.$message.success("修改成功");
}) })
}); });
}, },
handleAvatarSuccess(res, file) { handleUploadSuccess(res, file) {
console.log(res); this.userInfo.headImage = res.data.originUrl;
this.loading.close(); this.userInfo.headImageThumb = res.data.thumbUrl;
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;
} }
}, },
props: { props: {
@ -119,7 +102,7 @@
} }
</script> </script>
<style lang="scss"> <style lang="scss" >
.setting { .setting {
.avatar-uploader { .avatar-uploader {

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

@ -60,9 +60,18 @@ export default {
chat.unreadCount++; 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){ setChatUserInfo(state, userInfo){
console.log(userInfo)
for(let i in state.chats){ for(let i in state.chats){
if(state.chats[i].targetId == userInfo.id){ if(state.chats[i].targetId == userInfo.id){
state.chats[i].headImage = userInfo.headImageThumb; state.chats[i].headImage = userInfo.headImageThumb;

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

@ -9,42 +9,46 @@
</el-row> </el-row>
</el-header> </el-header>
<el-main> <el-main>
<div v-for="(chat,index) in $store.state.chatStore.chats" :key="chat.targetId"> <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)" <chat-item :chat="chat" :index="index" @click.native="onClickItem(index)" @del="onDelItem(chat,index)" :active="index === chatStore.activeIndex"></chat-item>
:active="index === $store.state.chatStore.activeIndex"></chat-item>
</div> </div>
</el-main> </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">
{{titleName}} {{activeChat.showName}}
</el-header> </el-header>
<el-main class="im-chat-main" id="chatScrollBox"> <el-main class="im-chat-main" id="chatScrollBox">
<div class="im-chat-box"> <div class="im-chat-box">
<ul> <ul>
<li v-for="item in messages" :key="item.id" <li v-for="msgInfo in activeChat.messages" :key="msgInfo.id">
:class="{ 'im-chat-mine': item.sendUserId == $store.state.userStore.userInfo.id }"> <message-item :mine="msgInfo.sendUserId == $store.state.userStore.userInfo.id"
<div class="head-image"> :headImage="headImage(msgInfo)"
<head-image :url="headImage(item)" ></head-image> :showName="showName(msgInfo)" :msgInfo="msgInfo">
</div> </message-item>
<div class="im-msg-content"> </li>
<div class="im-msg-top"> </ul>
<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> </div>
</el-main> </el-main>
<el-footer height="25%" class="im-chat-footer"> <el-footer height="25%" class="im-chat-footer">
<div class="chat-tool-bar"></div> <div class="chat-tool-bar">
<textarea v-model="messageContent" ref="sendBox" class="textarea" @keyup.enter="onSendMessage()"></textarea> <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"> <div class="im-chat-send">
<el-button type="primary" @click="onSendMessage()">发送</el-button> <el-button type="primary" @click="onSendMessage()">发送</el-button>
</div> </div>
</el-footer> </el-footer>
@ -53,16 +57,20 @@
</template> </template>
<script> <script>
import ChatItem from "../components/ChatItem.vue"; import ChatItem from "../components/chat/ChatItem.vue";
import ChatTime from "../components/ChatTime.vue"; import ChatTime from "../components/chat/ChatTime.vue";
import HeadImage from "../components/HeadImage.vue"; import MessageItem from "../components/chat/MessageItem.vue";
import HeadImage from "../components/common/HeadImage.vue";
import FileUpload from "../components/common/FileUpload.vue";
export default { export default {
name: "chat", name: "chat",
components: { components: {
ChatItem, ChatItem,
ChatTime, ChatTime,
HeadImage HeadImage,
FileUpload,
MessageItem
}, },
data() { data() {
return { return {
@ -83,16 +91,76 @@
let chat = this.chatStore.chats[index]; let chat = this.chatStore.chats[index];
if (userInfo.headImageThumb != chat.headImage || if (userInfo.headImageThumb != chat.headImage ||
userInfo.nickName != chat.showName) { userInfo.nickName != chat.showName) {
this.updateFriendInfo(userInfo,index) this.updateFriendInfo(userInfo, index)
} }
}) })
}, },
onSendMessage() { onSendMessage() {
let msgInfo = { let msgInfo = {
recvUserId: this.$store.state.chatStore.chats[this.$store.state.chatStore.activeIndex].targetId, recvUserId: this.activeChat.targetId,
content: this.messageContent, content: this.messageContent,
type: 0 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({ this.$http({
url: '/api/message/single/send', url: '/api/message/single/send',
method: 'post', method: 'post',
@ -103,25 +171,16 @@
msgInfo.sendTime = new Date().getTime(); msgInfo.sendTime = new Date().getTime();
msgInfo.sendUserId = this.$store.state.userStore.userInfo.id; msgInfo.sendUserId = this.$store.state.userStore.userInfo.id;
msgInfo.selfSend = true; msgInfo.selfSend = true;
console.log(msgInfo); msgInfo.loadStatus = "ok";
this.$store.commit("insertMessage", msgInfo); this.$store.commit("insertMessage", msgInfo);
// //
this.$refs.sendBox.focus(); this.$refs.sendBox.focus();
// //
this.$nextTick(() => { this.scrollToBottom();
const div = document.getElementById("chatScrollBox");
div.scrollTop = div.scrollHeight;
});
}) })
}, },
onDelItem(chat,index){ updateFriendInfo(userInfo, index) {
this.$store.commit("removeChat",index); let friendsInfo = {
},
updateFriendInfo(userInfo,index){
let friendsInfo={
friendId: userInfo.id, friendId: userInfo.id,
friendNickName: userInfo.nickName, friendNickName: userInfo.nickName,
friendHeadImage: userInfo.headImageThumb friendHeadImage: userInfo.headImageThumb
@ -131,73 +190,70 @@
method: "put", method: "put",
data: friendsInfo data: friendsInfo
}).then(() => { }).then(() => {
this.$store.commit("updateFriends",friendsInfo); this.$store.commit("updateFriends", friendsInfo);
this.$store.commit("setChatUserInfo",userInfo); this.$store.commit("setChatUserInfo", userInfo);
}) })
}, },
showName(msg) { showName(msg) {
if (msg.sendUserId == this.$store.state.userStore.userInfo.id) { if (msg.sendUserId == this.$store.state.userStore.userInfo.id) {
return this.$store.state.userStore.userInfo.nickName; 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){ headImage(msg) {
if(msg.sendUserId == this.$store.state.userStore.userInfo.id){
if (msg.sendUserId == this.$store.state.userStore.userInfo.id) {
return this.$store.state.userStore.userInfo.headImageThumb; 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: { computed: {
chatStore(){ chatStore() {
return this.$store.state.chatStore; return this.$store.state.chatStore;
}, },
messages() { activeChat() {
let index = this.$store.state.chatStore.activeIndex; let index = this.chatStore.activeIndex;
let chats = this.$store.state.chatStore.chats let chats = this.chatStore.chats
if (index >= 0 && chats.length > 0) { if (index >= 0 && chats.length > 0) {
console.log(chats[index].messages) return chats[index];
return chats[index].messages;
} }
return []; return this.emptyChat;
}, },
titleName(){ emptyChat() {
let index = this.$store.state.chatStore.activeIndex; //
let chats = this.$store.state.chatStore.chats; return {
if(index>=0 && chats.length > 0){ targetId: -1,
let chats = this.$store.state.chatStore.chats; showName: "",
return chats[index].showName; headImage: "",
messages: []
} }
return "";
} }
} }
} }
</script> </script>
<style scoped 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: #eeeeee;
width: 3rem; width: 3rem;
.el-header { .el-header {
padding: 5px; padding: 5px;
background-color: white; background-color: white;
line-height: 50px; line-height: 50px;
} }
.el-main { .el-main {
padding: 0 padding: 0
} }
@ -206,6 +262,7 @@
.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;
@ -215,136 +272,49 @@
.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: 10px; padding: 20px;
li { li {
position: relative; list-style-type:none;
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-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 { .im-chat-footer {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
padding: 0; padding: 0;
.chat-tool-bar { .chat-tool-bar {
display: flex;
position: relative;
width: 100%; width: 100%;
height: 40px; 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; box-sizing: border-box;
padding: 5px; padding: 5px;
width: 100%; width: 100%;
@ -353,13 +323,12 @@
background-color: #f8f8f8 !important; background-color: #f8f8f8 !important;
outline-color: rgba(83, 160, 231, 0.61); outline-color: rgba(83, 160, 231, 0.61);
} }
.im-chat-send { .im-chat-send {
text-align: right; text-align: right;
padding: 7px; padding: 7px;
} }
} }
} }
} }
</style> </style>

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

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

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

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

Loading…
Cancel
Save