Browse Source

前端代码格式化(使用vscode默认格式)

master
xsx 1 year ago
parent
commit
cc627ec45a
  1. 783
      im-uniapp/App.vue
  2. 18
      im-uniapp/common/date.js
  3. 52
      im-uniapp/common/enums.js
  4. 24
      im-uniapp/common/messageType.js
  5. 8
      im-uniapp/common/recorder-h5.js
  6. 4
      im-uniapp/common/request.js
  7. 10
      im-uniapp/common/wssocket.js
  8. 55
      im-uniapp/components/bar/arrow-bar.vue
  9. 12
      im-uniapp/components/bar/bar-group.vue
  10. 102
      im-uniapp/components/bar/btn-bar.vue
  11. 86
      im-uniapp/components/bar/switch-bar.vue
  12. 239
      im-uniapp/components/chat-at-box/chat-at-box.vue
  13. 173
      im-uniapp/components/chat-group-readed/chat-group-readed.vue
  14. 263
      im-uniapp/components/chat-item/chat-item.vue
  15. 685
      im-uniapp/components/chat-message-item/chat-message-item.vue
  16. 372
      im-uniapp/components/chat-record/chat-record.vue
  17. 187
      im-uniapp/components/file-upload/file-upload.vue
  18. 97
      im-uniapp/components/friend-item/friend-item.vue
  19. 79
      im-uniapp/components/group-item/group-item.vue
  20. 223
      im-uniapp/components/group-member-selector/group-member-selector.vue
  21. 105
      im-uniapp/components/group-rtc-join/group-rtc-join.vue
  22. 182
      im-uniapp/components/head-image/head-image.vue
  23. 151
      im-uniapp/components/image-upload/image-upload.vue
  24. 84
      im-uniapp/components/loading/loading.vue
  25. 9
      im-uniapp/components/nav-bar/nav-bar.vue
  26. 188
      im-uniapp/components/pop-menu/pop-menu.vue
  27. 17
      im-uniapp/main.js
  28. 167
      im-uniapp/manifest.json
  29. 2
      im-uniapp/package.json
  30. 103
      im-uniapp/pages.json
  31. 1528
      im-uniapp/pages/chat/chat-box.vue
  32. 230
      im-uniapp/pages/chat/chat-group-video.vue
  33. 169
      im-uniapp/pages/chat/chat-private-video.vue
  34. 227
      im-uniapp/pages/chat/chat.vue
  35. 300
      im-uniapp/pages/common/user-info.vue
  36. 163
      im-uniapp/pages/friend/friend-add.vue
  37. 228
      im-uniapp/pages/friend/friend.vue
  38. 271
      im-uniapp/pages/group/group-edit.vue
  39. 401
      im-uniapp/pages/group/group-info.vue
  40. 257
      im-uniapp/pages/group/group-invite.vue
  41. 238
      im-uniapp/pages/group/group-member.vue
  42. 79
      im-uniapp/pages/group/group.vue
  43. 136
      im-uniapp/pages/login/login.vue
  44. 128
      im-uniapp/pages/mine/mine-edit.vue
  45. 165
      im-uniapp/pages/mine/mine-password.vue
  46. 236
      im-uniapp/pages/mine/mine.vue
  47. 198
      im-uniapp/pages/register/register.vue
  48. 32
      im-uniapp/store/chatStore.js
  49. 2
      im-uniapp/store/userStore.js
  50. 2
      im-uniapp/vite.config.js
  51. 1
      im-web/src/view/Login.vue

783
im-uniapp/App.vue

@ -1,429 +1,430 @@
<script> <script>
import App from './App' import App from './App'
import http from './common/request'; import http from './common/request';
import * as msgType from './common/messageType'; import * as msgType from './common/messageType';
import * as enums from './common/enums'; import * as enums from './common/enums';
import * as wsApi from './common/wssocket'; import * as wsApi from './common/wssocket';
import UNI_APP from '@/.env.js' import UNI_APP from '@/.env.js'
export default { export default {
data() { data() {
return { return {
isExit: false, // 退 isExit: false, // 退
audioTip: null, audioTip: null,
reconnecting: false // reconnecting: false //
} }
},
methods: {
init() {
this.isExit = false;
//
this.loadStore().then(() => {
// websocket
this.initWebSocket();
}).catch((e) => {
console.log(e);
this.exit();
})
}, },
methods: { initWebSocket() {
init() { let loginInfo = uni.getStorageSync("loginInfo")
this.isExit = false; wsApi.init();
// wsApi.connect(UNI_APP.WS_URL, loginInfo.accessToken);
this.loadStore().then(() => { wsApi.onConnect(() => {
// websocket //
this.initWebSocket(); if (this.reconnecting) {
}).catch((e) => { this.reconnecting = false;
console.log(e); uni.showToast({
this.exit(); title: "已重新连接",
}) icon: 'none'
},
initWebSocket() {
let loginInfo = uni.getStorageSync("loginInfo")
wsApi.init();
wsApi.connect(UNI_APP.WS_URL, loginInfo.accessToken);
wsApi.onConnect(() => {
//
if (this.reconnecting) {
this.reconnecting = false;
uni.showToast({
title: "已重新连接",
icon: 'none'
})
}
// 线
this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId);
this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId);
});
wsApi.onMessage((cmd, msgInfo) => {
if (cmd == 2) {
// 线
uni.showModal({
content: '您已在其他地方登陆,将被强制下线',
showCancel: false,
})
this.exit();
} else if (cmd == 3) {
//
this.handlePrivateMessage(msgInfo);
} else if (cmd == 4) {
//
this.handleGroupMessage(msgInfo);
} else if (cmd == 5) {
//
this.handleSystemMessage(msgInfo);
}
});
wsApi.onClose((res) => {
console.log("ws断开", res);
//
this.reconnectWs();
})
},
loadStore() {
return this.userStore.loadUser().then(() => {
const promises = [];
promises.push(this.friendStore.loadFriend());
promises.push(this.groupStore.loadGroup());
promises.push(this.chatStore.loadChat());
promises.push(this.configStore.loadConfig());
return Promise.all(promises);
})
},
unloadStore(){
this.friendStore.clear();
this.groupStore.clear();
this.chatStore.clear();
this.configStore.clear();
this.userStore.clear();
},
pullPrivateOfflineMessage(minId) {
this.chatStore.setLoadingPrivateMsg(true)
http({
url: "/message/private/pullOfflineMessage?minId=" + minId,
method: 'GET'
}).catch(() => {
this.chatStore.setLoadingPrivateMsg(false)
})
},
pullGroupOfflineMessage(minId) {
this.chatStore.setLoadingGroupMsg(true)
http({
url: "/message/group/pullOfflineMessage?minId=" + minId,
method: 'GET'
}).catch(() => {
this.chatStore.setLoadingGroupMsg(false)
})
},
handlePrivateMessage(msg) {
//
if (msg.type == enums.MESSAGE_TYPE.LOADING) {
this.chatStore.setLoadingPrivateMsg(JSON.parse(msg.content))
return;
}
//
if (msg.type == enums.MESSAGE_TYPE.READED) {
this.chatStore.resetUnreadCount({
type: 'PRIVATE',
targetId: msg.recvId
})
return;
}
// ,
if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
this.chatStore.readedMessage({
friendId: msg.sendId
}) })
return;
}
//
msg.selfSend = msg.sendId == this.userStore.userInfo.id;
// id
let friendId = msg.selfSend ? msg.recvId : msg.sendId;
this.loadFriendInfo(friendId, (friend) => {
this.insertPrivateMessage(friend, msg);
})
},
insertPrivateMessage(friend, msg) {
//
if (msgType.isRtcPrivate(msg.type)) {
// #ifdef MP-WEIXIN
//
return;
// #endif
//
let delayTime = 100;
if (msg.type == enums.MESSAGE_TYPE.RTC_CALL_VOICE ||
msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO) {
let mode = msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO ? "video" : "voice";
let pages = getCurrentPages();
let curPage = pages[pages.length - 1].route;
if (curPage != "pages/chat/chat-private-video") {
const friendInfo = encodeURIComponent(JSON.stringify(friend));
uni.navigateTo({
url: `/pages/chat/chat-private-video?mode=${mode}&friend=${friendInfo}&isHost=false`
})
delayTime = 500;
}
}
setTimeout(() => {
uni.$emit('WS_RTC_PRIVATE', msg);
}, delayTime)
return;
} }
let chatInfo = { // 线
type: 'PRIVATE', this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId);
targetId: friend.id, this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId);
showName: friend.nickName, });
headImage: friend.headImage wsApi.onMessage((cmd, msgInfo) => {
}; if (cmd == 2) {
// // 线
this.chatStore.openChat(chatInfo);
//
this.chatStore.insertMessage(msg);
//
this.playAudioTip();
},
handleGroupMessage(msg) {
//
if (msg.type == enums.MESSAGE_TYPE.LOADING) {
this.chatStore.setLoadingGroupMsg(JSON.parse(msg.content))
return;
}
//
if (msg.type == enums.MESSAGE_TYPE.READED) {
//
let chatInfo = {
type: 'GROUP',
targetId: msg.groupId
}
this.chatStore.resetUnreadCount(chatInfo)
return;
}
//
if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
//
let msgInfo = {
id: msg.id,
groupId: msg.groupId,
readedCount: msg.readedCount,
receiptOk: msg.receiptOk
};
this.chatStore.updateMessage(msgInfo)
return;
}
//
msg.selfSend = msg.sendId == this.userStore.userInfo.id;
this.loadGroupInfo(msg.groupId, (group) => {
//
this.insertGroupMessage(group, msg);
})
},
handleSystemMessage(msg) {
if (msg.type == enums.MESSAGE_TYPE.USER_BANNED) {
//
wsApi.close(3099);
uni.showModal({ uni.showModal({
content: '您的账号已被管理员封禁,原因:' + msg.content, content: '您已在其他地方登陆,将被强制下线',
showCancel: false, showCancel: false,
}) })
this.exit(); this.exit();
} else if (cmd == 3) {
//
this.handlePrivateMessage(msgInfo);
} else if (cmd == 4) {
//
this.handleGroupMessage(msgInfo);
} else if (cmd == 5) {
//
this.handleSystemMessage(msgInfo);
} }
}, });
insertGroupMessage(group, msg) { wsApi.onClose((res) => {
// console.log("ws断开", res);
if (msgType.isRtcGroup(msg.type)) { //
// #ifdef MP-WEIXIN this.reconnectWs();
//
return; })
// #endif },
// loadStore() {
let delayTime = 100; return this.userStore.loadUser().then(() => {
if (msg.type == enums.MESSAGE_TYPE.RTC_GROUP_SETUP) { const promises = [];
let pages = getCurrentPages(); promises.push(this.friendStore.loadFriend());
let curPage = pages[pages.length - 1].route; promises.push(this.groupStore.loadGroup());
if (curPage != "pages/chat/chat-group-video") { promises.push(this.chatStore.loadChat());
const userInfos = encodeURIComponent(msg.content); promises.push(this.configStore.loadConfig());
const inviterId = msg.sendId; return Promise.all(promises);
const groupId = msg.groupId })
uni.navigateTo({ },
url: `/pages/chat/chat-group-video?groupId=${groupId}&isHost=false unloadStore() {
&inviterId=${inviterId}&userInfos=${userInfos}` this.friendStore.clear();
}) this.groupStore.clear();
delayTime = 500; this.chatStore.clear();
} this.configStore.clear();
this.userStore.clear();
},
pullPrivateOfflineMessage(minId) {
this.chatStore.setLoadingPrivateMsg(true)
http({
url: "/message/private/pullOfflineMessage?minId=" + minId,
method: 'GET'
}).catch(() => {
this.chatStore.setLoadingPrivateMsg(false)
})
},
pullGroupOfflineMessage(minId) {
this.chatStore.setLoadingGroupMsg(true)
http({
url: "/message/group/pullOfflineMessage?minId=" + minId,
method: 'GET'
}).catch(() => {
this.chatStore.setLoadingGroupMsg(false)
})
},
handlePrivateMessage(msg) {
//
if (msg.type == enums.MESSAGE_TYPE.LOADING) {
this.chatStore.setLoadingPrivateMsg(JSON.parse(msg.content))
return;
}
//
if (msg.type == enums.MESSAGE_TYPE.READED) {
this.chatStore.resetUnreadCount({
type: 'PRIVATE',
targetId: msg.recvId
})
return;
}
// ,
if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
this.chatStore.readedMessage({
friendId: msg.sendId
})
return;
}
//
msg.selfSend = msg.sendId == this.userStore.userInfo.id;
// id
let friendId = msg.selfSend ? msg.recvId : msg.sendId;
this.loadFriendInfo(friendId, (friend) => {
this.insertPrivateMessage(friend, msg);
})
},
insertPrivateMessage(friend, msg) {
//
if (msgType.isRtcPrivate(msg.type)) {
// #ifdef MP-WEIXIN
//
return;
// #endif
//
let delayTime = 100;
if (msg.type == enums.MESSAGE_TYPE.RTC_CALL_VOICE ||
msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO) {
let mode = msg.type == enums.MESSAGE_TYPE.RTC_CALL_VIDEO ? "video" : "voice";
let pages = getCurrentPages();
let curPage = pages[pages.length - 1].route;
if (curPage != "pages/chat/chat-private-video") {
const friendInfo = encodeURIComponent(JSON.stringify(friend));
uni.navigateTo({
url: `/pages/chat/chat-private-video?mode=${mode}&friend=${friendInfo}&isHost=false`
})
delayTime = 500;
} }
// chat-group-video
setTimeout(() => {
uni.$emit('WS_RTC_GROUP', msg);
}, delayTime)
return;
} }
setTimeout(() => {
uni.$emit('WS_RTC_PRIVATE', msg);
}, delayTime)
return;
}
let chatInfo = {
type: 'PRIVATE',
targetId: friend.id,
showName: friend.nickName,
headImage: friend.headImage
};
//
this.chatStore.openChat(chatInfo);
//
this.chatStore.insertMessage(msg);
//
this.playAudioTip();
},
handleGroupMessage(msg) {
//
if (msg.type == enums.MESSAGE_TYPE.LOADING) {
this.chatStore.setLoadingGroupMsg(JSON.parse(msg.content))
return;
}
//
if (msg.type == enums.MESSAGE_TYPE.READED) {
//
let chatInfo = { let chatInfo = {
type: 'GROUP', type: 'GROUP',
targetId: group.id, targetId: msg.groupId
showName: group.showGroupName,
headImage: group.headImageThumb
};
//
this.chatStore.openChat(chatInfo);
//
this.chatStore.insertMessage(msg);
//
this.playAudioTip();
},
loadFriendInfo(id, callback) {
let friend = this.friendStore.findFriend(id);
if (friend) {
callback(friend);
} else {
http({
url: `/friend/find/${id}`,
method: 'GET'
}).then((friend) => {
this.friendStore.addFriend(friend);
callback(friend)
})
}
},
loadGroupInfo(id, callback) {
let group = this.groupStore.findGroup(id);
if (group) {
callback(group);
} else {
http({
url: `/group/find/${id}`,
method: 'GET'
}).then((group) => {
this.groupStore.addGroup(group);
callback(group)
})
} }
}, this.chatStore.resetUnreadCount(chatInfo)
exit() { return;
console.log("exit"); }
this.isExit = true; //
if (msg.type == enums.MESSAGE_TYPE.RECEIPT) {
//
let msgInfo = {
id: msg.id,
groupId: msg.groupId,
readedCount: msg.readedCount,
receiptOk: msg.receiptOk
};
this.chatStore.updateMessage(msgInfo)
return;
}
//
msg.selfSend = msg.sendId == this.userStore.userInfo.id;
this.loadGroupInfo(msg.groupId, (group) => {
//
this.insertGroupMessage(group, msg);
})
},
handleSystemMessage(msg) {
if (msg.type == enums.MESSAGE_TYPE.USER_BANNED) {
//
wsApi.close(3099); wsApi.close(3099);
uni.removeStorageSync("loginInfo"); uni.showModal({
uni.reLaunch({ content: '您的账号已被管理员封禁,原因:' + msg.content,
url: "/pages/login/login" showCancel: false,
}) })
this.unloadStore(); this.exit();
}, }
playAudioTip() { },
// insertGroupMessage(group, msg) {
// this.audioTip = uni.createInnerAudioContext(); //
// this.audioTip.src = "/static/audio/tip.wav"; if (msgType.isRtcGroup(msg.type)) {
// this.audioTip.play(); // #ifdef MP-WEIXIN
}, //
refreshToken(loginInfo) { return;
return new Promise((resolve, reject) => { // #endif
if (!loginInfo || !loginInfo.refreshToken) { //
reject(); let delayTime = 100;
return; if (msg.type == enums.MESSAGE_TYPE.RTC_GROUP_SETUP) {
let pages = getCurrentPages();
let curPage = pages[pages.length - 1].route;
if (curPage != "pages/chat/chat-group-video") {
const userInfos = encodeURIComponent(msg.content);
const inviterId = msg.sendId;
const groupId = msg.groupId
uni.navigateTo({
url: `/pages/chat/chat-group-video?groupId=${groupId}&isHost=false
&inviterId=${inviterId}&userInfos=${userInfos}`
})
delayTime = 500;
} }
http({
url: '/refreshToken',
method: 'PUT',
header: {
refreshToken: loginInfo.refreshToken
}
}).then((newLoginInfo) => {
uni.setStorageSync("loginInfo", newLoginInfo)
resolve()
}).catch((e) => {
reject(e)
})
})
},
reconnectWs() {
// 退
if (this.isExit) {
return;
} }
// // chat-group-video
this.reconnecting = true; setTimeout(() => {
// token uni.$emit('WS_RTC_GROUP', msg);
this.reloadUserInfo().then((userInfo) => { }, delayTime)
uni.showToast({ return;
title: '连接已断开,尝试重新连接...', }
icon: 'none',
}) let chatInfo = {
this.userStore.setUserInfo(userInfo); type: 'GROUP',
// targetId: group.id,
let loginInfo = uni.getStorageSync("loginInfo") showName: group.showGroupName,
wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken); headImage: group.headImageThumb
}).catch(() => { };
// 5s //
setTimeout(() => { this.chatStore.openChat(chatInfo);
this.reconnectWs(); //
}, 5000) this.chatStore.insertMessage(msg);
//
this.playAudioTip();
},
loadFriendInfo(id, callback) {
let friend = this.friendStore.findFriend(id);
if (friend) {
callback(friend);
} else {
http({
url: `/friend/find/${id}`,
method: 'GET'
}).then((friend) => {
this.friendStore.addFriend(friend);
callback(friend)
}) })
}, }
reloadUserInfo() { },
return http({ loadGroupInfo(id, callback) {
url: '/user/self', let group = this.groupStore.findGroup(id);
if (group) {
callback(group);
} else {
http({
url: `/group/find/${id}`,
method: 'GET' method: 'GET'
}).then((group) => {
this.groupStore.addGroup(group);
callback(group)
}) })
} }
}, },
onLaunch() { exit() {
this.$mountStore(); console.log("exit");
// this.isExit = true;
let loginInfo = uni.getStorageSync("loginInfo") wsApi.close(3099);
this.refreshToken(loginInfo).then(() => { uni.removeStorageSync("loginInfo");
// uni.reLaunch({
this.init(); url: "/pages/login/login"
// })
uni.switchTab({ this.unloadStore();
url: "/pages/chat/chat" },
playAudioTip() {
//
// this.audioTip = uni.createInnerAudioContext();
// this.audioTip.src = "/static/audio/tip.wav";
// this.audioTip.play();
},
refreshToken(loginInfo) {
return new Promise((resolve, reject) => {
if (!loginInfo || !loginInfo.refreshToken) {
reject();
return;
}
http({
url: '/refreshToken',
method: 'PUT',
header: {
refreshToken: loginInfo.refreshToken
}
}).then((newLoginInfo) => {
uni.setStorageSync("loginInfo", newLoginInfo)
resolve()
}).catch((e) => {
reject(e)
}) })
}).catch(() => { })
// },
// #ifdef H5 reconnectWs() {
uni.navigateTo({ // 退
url: "/pages/login/login" if (this.isExit) {
return;
}
//
this.reconnecting = true;
// token
this.reloadUserInfo().then((userInfo) => {
uni.showToast({
title: '连接已断开,尝试重新连接...',
icon: 'none',
}) })
// #endif this.userStore.setUserInfo(userInfo);
//
let loginInfo = uni.getStorageSync("loginInfo")
wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken);
}).catch(() => {
// 5s
setTimeout(() => {
this.reconnectWs();
}, 5000)
})
},
reloadUserInfo() {
return http({
url: '/user/self',
method: 'GET'
}) })
} }
},
onLaunch() {
this.$mountStore();
//
let loginInfo = uni.getStorageSync("loginInfo")
this.refreshToken(loginInfo).then(() => {
//
this.init();
//
uni.switchTab({
url: "/pages/chat/chat"
})
}).catch(() => {
//
// #ifdef H5
uni.navigateTo({
url: "/pages/login/login"
})
// #endif
})
} }
}
</script> </script>
<style lang="scss"> <style lang="scss">
@import "@/uni_modules/uview-plus/index.scss"; @import "@/uni_modules/uview-plus/index.scss";
@import "@/im.scss"; @import "@/im.scss";
@import url('./static/icon/iconfont.css'); @import url('./static/icon/iconfont.css');
// #ifdef H5 // #ifdef H5
uni-page-head { uni-page-head {
display: none; // h5 display: none; // h5
} }
// #endif
.tab-page { // #endif
position: relative;
display: flex;
flex-direction: column;
// #ifdef H5
height: calc(100vh - 50px - $im-nav-bar-height); // h5100vh
top: $im-nav-bar-height;
// #endif
// #ifndef H5 .tab-page {
height: calc(100vh - var(--status-bar-height) - $im-nav-bar-height); // app position: relative;
top: calc($im-nav-bar-height + var(--status-bar-height)); display: flex;
// #endif flex-direction: column;
color: $im-text-color; // #ifdef H5
background-color: $im-bg; height: calc(100vh - 50px - $im-nav-bar-height); // h5100vh
font-size: $im-font-size; top: $im-nav-bar-height;
font-family: $font-family; // #endif
}
.page { // #ifndef H5
position: relative; height: calc(100vh - var(--status-bar-height) - $im-nav-bar-height); // app
// #ifdef H5 top: calc($im-nav-bar-height + var(--status-bar-height));
height: calc(100vh - $im-nav-bar-height); // app // #endif
top: $im-nav-bar-height; color: $im-text-color;
// #endif background-color: $im-bg;
// #ifndef H5 font-size: $im-font-size;
height: calc(100vh - var(--status-bar-height) - $im-nav-bar-height); // app font-family: $font-family;
top: calc($im-nav-bar-height + var(--status-bar-height)); }
// #endif
color: $im-text-color; .page {
background-color: $im-bg; position: relative;
font-size: $im-font-size; // #ifdef H5
font-family: $font-family; height: calc(100vh - $im-nav-bar-height); // app
} top: $im-nav-bar-height;
// #endif
// #ifndef H5
height: calc(100vh - var(--status-bar-height) - $im-nav-bar-height); // app
top: calc($im-nav-bar-height + var(--status-bar-height));
// #endif
color: $im-text-color;
background-color: $im-bg;
font-size: $im-font-size;
font-family: $font-family;
}
</style> </style>

18
im-uniapp/common/date.js

@ -5,23 +5,23 @@ let toTimeText = (timeStamp, simple) => {
var timeText = ''; var timeText = '';
if (timeDiff <= 60000) { //一分钟内 if (timeDiff <= 60000) { //一分钟内
timeText = '刚刚'; timeText = '刚刚';
} else if (timeDiff > 60000 && timeDiff < 3600000) { } else if (timeDiff > 60000 && timeDiff < 3600000) {
//1小时内 //1小时内
timeText = Math.floor(timeDiff / 60000) + '分钟前'; timeText = Math.floor(timeDiff / 60000) + '分钟前';
} else if (timeDiff >= 3600000 && timeDiff < 86400000 && !isYestday(dateTime)) { } else if (timeDiff >= 3600000 && timeDiff < 86400000 && !isYestday(dateTime)) {
//今日 //今日
timeText = formatDateTime(dateTime).substr(11, 5); timeText = formatDateTime(dateTime).substr(11, 5);
} else if (isYestday(dateTime)) { } else if (isYestday(dateTime)) {
//昨天 //昨天
timeText = '昨天' + formatDateTime(dateTime).substr(11, 5); timeText = '昨天' + formatDateTime(dateTime).substr(11, 5);
} else if (isYear(dateTime)) { } else if (isYear(dateTime)) {
//今年 //今年
timeText = formatDateTime(dateTime).substr(5, simple ? 5 : 14); timeText = formatDateTime(dateTime).substr(5, simple ? 5 : 14);
} else { } else {
//不属于今年 //不属于今年
timeText = formatDateTime(dateTime); timeText = formatDateTime(dateTime);
if(simple){ if (simple) {
timeText = timeText.substr(2,8); timeText = timeText.substr(2, 8);
} }
} }
return timeText; return timeText;
@ -58,7 +58,7 @@ let formatDateTime = (date) => {
} }
export{ export {
toTimeText, toTimeText,
isYestday, isYestday,
isYear, isYear,

52
im-uniapp/common/enums.js

@ -2,18 +2,18 @@
const MESSAGE_TYPE = { const MESSAGE_TYPE = {
TEXT: 0, TEXT: 0,
IMAGE: 1, IMAGE: 1,
FILE:2, FILE: 2,
AUDIO:3, AUDIO: 3,
VIDEO:4, VIDEO: 4,
RECALL:10, RECALL: 10,
READED:11, READED: 11,
RECEIPT:12, RECEIPT: 12,
TIP_TIME:20, TIP_TIME: 20,
TIP_TEXT:21, TIP_TEXT: 21,
LOADING:30, LOADING: 30,
ACT_RT_VOICE:40, ACT_RT_VOICE: 40,
ACT_RT_VIDEO:41, ACT_RT_VIDEO: 41,
USER_BANNED:50, USER_BANNED: 50,
RTC_CALL_VOICE: 100, RTC_CALL_VOICE: 100,
RTC_CALL_VIDEO: 101, RTC_CALL_VIDEO: 101,
RTC_ACCEPT: 102, RTC_ACCEPT: 102,
@ -22,18 +22,18 @@ const MESSAGE_TYPE = {
RTC_FAILED: 105, RTC_FAILED: 105,
RTC_HANDUP: 106, RTC_HANDUP: 106,
RTC_CANDIDATE: 107, RTC_CANDIDATE: 107,
RTC_GROUP_SETUP:200, RTC_GROUP_SETUP: 200,
RTC_GROUP_ACCEPT:201, RTC_GROUP_ACCEPT: 201,
RTC_GROUP_REJECT:202, RTC_GROUP_REJECT: 202,
RTC_GROUP_FAILED:203, RTC_GROUP_FAILED: 203,
RTC_GROUP_CANCEL:204, RTC_GROUP_CANCEL: 204,
RTC_GROUP_QUIT:205, RTC_GROUP_QUIT: 205,
RTC_GROUP_INVITE:206, RTC_GROUP_INVITE: 206,
RTC_GROUP_JOIN:207, RTC_GROUP_JOIN: 207,
RTC_GROUP_OFFER:208, RTC_GROUP_OFFER: 208,
RTC_GROUP_ANSWER:209, RTC_GROUP_ANSWER: 209,
RTC_GROUP_CANDIDATE:210, RTC_GROUP_CANDIDATE: 210,
RTC_GROUP_DEVICE:211 RTC_GROUP_DEVICE: 211
} }
const USER_STATE = { const USER_STATE = {
@ -50,8 +50,8 @@ const TERMINAL_TYPE = {
const MESSAGE_STATUS = { const MESSAGE_STATUS = {
UNSEND: 0, UNSEND: 0,
SENDED: 1, SENDED: 1,
RECALL:2, RECALL: 2,
READED:3 READED: 3
} }
export { export {

24
im-uniapp/common/messageType.js

@ -1,32 +1,32 @@
// 是否普通消息 // 是否普通消息
let isNormal = function(type){ let isNormal = function (type) {
return type>=0 && type < 10; return type >= 0 && type < 10;
} }
// 是否状态消息 // 是否状态消息
let isStatus = function(type){ let isStatus = function (type) {
return type>=10 && type < 20; return type >= 10 && type < 20;
} }
// 是否提示消息 // 是否提示消息
let isTip = function(type){ let isTip = function (type) {
return type>=20 && type < 30; return type >= 20 && type < 30;
} }
// 操作交互类消息 // 操作交互类消息
let isAction = function(type){ let isAction = function (type) {
return type>=40 && type < 50; return type >= 40 && type < 50;
} }
// 单人通话信令 // 单人通话信令
let isRtcPrivate = function(type){ let isRtcPrivate = function (type) {
return type>=100 && type < 200; return type >= 100 && type < 200;
} }
// 多人通话信令 // 多人通话信令
let isRtcGroup = function(type){ let isRtcGroup = function (type) {
return type>=200 && type < 300; return type >= 200 && type < 300;
} }

8
im-uniapp/common/recorder-h5.js

@ -3,7 +3,7 @@ import UNI_APP from '@/.env.js';
let rc = null; let rc = null;
let start = () => { let start = () => {
if(rc != null){ if (rc != null) {
close(); close();
} }
rc = new Recorder(); rc = new Recorder();
@ -22,7 +22,7 @@ let close = () => {
let upload = () => { let upload = () => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
const wavBlob = rc.getWAVBlob(); const wavBlob = rc.getWAVBlob();
const newbolb = new Blob([wavBlob], { type: 'audio/wav'}) const newbolb = new Blob([wavBlob], { type: 'audio/wav' })
const name = new Date().getDate() + '.wav'; const name = new Date().getDate() + '.wav';
const file = new File([newbolb], name) const file = new File([newbolb], name)
uni.uploadFile({ uni.uploadFile({
@ -34,10 +34,10 @@ let upload = () => {
name: 'file', name: 'file',
success: (res) => { success: (res) => {
let r = JSON.parse(res.data); let r = JSON.parse(res.data);
if(r.code != 200){ if (r.code != 200) {
console.log(res) console.log(res)
reject(r.message); reject(r.message);
}else { } else {
const data = { const data = {
duration: parseInt(rc.duration), duration: parseInt(rc.duration),
url: r.data url: r.data

4
im-uniapp/common/request.js

@ -11,7 +11,7 @@ const request = (options) => {
if (loginInfo) { if (loginInfo) {
header.accessToken = loginInfo.accessToken; header.accessToken = loginInfo.accessToken;
} }
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
uni.request({ uni.request({
url: UNI_APP.BASE_URL + options.url, url: UNI_APP.BASE_URL + options.url,
method: options.method || 'GET', method: options.method || 'GET',
@ -72,7 +72,7 @@ const request = (options) => {
const reqRefreshToken = (loginInfo) => { const reqRefreshToken = (loginInfo) => {
return new Promise(function(resolve, reject) { return new Promise(function (resolve, reject) {
uni.request({ uni.request({
method: 'PUT', method: 'PUT',
url: UNI_APP.BASE_URL + '/refreshToken', url: UNI_APP.BASE_URL + '/refreshToken',

10
im-uniapp/common/wssocket.js

@ -55,7 +55,7 @@ let init = () => {
console.log(e) console.log(e)
isConnect = false; isConnect = false;
// APP 应用切出超过一定时间(约1分钟)会触发报错,此处回调给应用进行重连 // APP 应用切出超过一定时间(约1分钟)会触发报错,此处回调给应用进行重连
closeCallBack && closeCallBack({code: 1006}); closeCallBack && closeCallBack({ code: 1006 });
}) })
}; };
@ -92,7 +92,7 @@ let reconnect = (wsurl, accessToken) => {
let timeDiff = new Date().getTime() - lastConnectTime.getTime() let timeDiff = new Date().getTime() - lastConnectTime.getTime()
let delay = timeDiff < 10000 ? 10000 - timeDiff : 0; let delay = timeDiff < 10000 ? 10000 - timeDiff : 0;
rec && clearTimeout(rec); rec && clearTimeout(rec);
rec = setTimeout(function() { rec = setTimeout(function () {
connect(wsurl, accessToken); connect(wsurl, accessToken);
}, delay); }, delay);
}; };
@ -119,7 +119,7 @@ let close = (code) => {
var heartCheck = { var heartCheck = {
timeout: 10000, //每段时间发送一次心跳包 这里设置为30s timeout: 10000, //每段时间发送一次心跳包 这里设置为30s
timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象) timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象)
start: function() { start: function () {
if (isConnect) { if (isConnect) {
console.log('发送WebSocket心跳') console.log('发送WebSocket心跳')
let heartBeat = { let heartBeat = {
@ -134,9 +134,9 @@ var heartCheck = {
}) })
} }
}, },
reset: function() { reset: function () {
clearTimeout(this.timeoutObj); clearTimeout(this.timeoutObj);
this.timeoutObj = setTimeout(function() { this.timeoutObj = setTimeout(function () {
heartCheck.start(); heartCheck.start();
}, this.timeout); }, this.timeout);
} }

55
im-uniapp/components/bar/arrow-bar.vue

@ -1,39 +1,40 @@
<template> <template>
<view class="arrow-bar"> <view class="arrow-bar">
<text class="title">{{title}}</text> <text class="title">{{ title }}</text>
<uni-icons class="arrow" type="right" size="16"></uni-icons> <uni-icons class="arrow" type="right" size="16"></uni-icons>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: "arrow-bar", name: "arrow-bar",
props: { props: {
title: { title: {
type: String, type: String,
required: true required: true
} }
}, },
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.arrow-bar { .arrow-bar {
width: 100%; width: 100%;
height: 90rpx; height: 90rpx;
font-size: 30rpx; font-size: 30rpx;
color: black; color: black;
margin-top: 5rpx; margin-top: 5rpx;
background-color: white; background-color: white;
line-height: 90rpx; line-height: 90rpx;
display: flex; display: flex;
.title {
flex: 1; .title {
margin-left: 40rpx; flex: 1;
} margin-left: 40rpx;
}
.arrow {
margin-right: 40rpx; .arrow {
} margin-right: 40rpx;
} }
}
</style> </style>

12
im-uniapp/components/bar/bar-group.vue

@ -5,13 +5,13 @@
</template> </template>
<script> <script>
export default{ export default {
name: "bar-group" name: "bar-group"
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.bar-group{ .bar-group {
margin: 20rpx 0; margin: 20rpx 0;
} }
</style> </style>

102
im-uniapp/components/bar/btn-bar.vue

@ -1,66 +1,68 @@
<template> <template>
<view class="btn-bar" :style="style"> <view class="btn-bar" :style="style">
<text v-if="icon" class="icon iconfont" :class="icon"></text> <text v-if="icon" class="icon iconfont" :class="icon"></text>
<text class="title">{{title}}</text> <text class="title">{{ title }}</text>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: "btn-bar", name: "btn-bar",
props: { props: {
title: { title: {
type: String, type: String,
required: true required: true
}, },
icon: { icon: {
type: String, type: String,
required: false required: false
}, },
type: { type: {
type: String, type: String,
default: "normal" default: "normal"
},
color: {
type: String,
default: "#000"
}
}, },
computed: { color: {
style() { type: String,
let color = "#000"; default: "#000"
switch (this.type) { }
case 'danger': },
color = "#f14747"; computed: {
break; style() {
case 'primary': let color = "#000";
color = "#35567f"; switch (this.type) {
break; case 'danger':
} color = "#f14747";
return `color: ${color};` break;
case 'primary':
color = "#35567f";
break;
} }
return `color: ${color};`
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.btn-bar { .btn-bar {
width: 100%; width: 100%;
height: 100rpx; height: 100rpx;
margin-top: 5rpx; margin-top: 5rpx;
background-color: white; background-color: white;
line-height: 100rpx; line-height: 100rpx;
text-align: center; text-align: center;
display: flex; display: flex;
justify-content: center; justify-content: center;
.icon {
font-size: 40rpx; .icon {
font-weight: 600; font-size: 40rpx;
margin-right: 10rpx; font-weight: 600;
} margin-right: 10rpx;
.title { }
font-size: 32rpx;
font-weight: 600; .title {
} font-size: 32rpx;
font-weight: 600;
} }
}
</style> </style>

86
im-uniapp/components/bar/switch-bar.vue

@ -1,61 +1,61 @@
<template> <template>
<view class="switch-bar"> <view class="switch-bar">
<text class="title">{{title}}</text> <text class="title">{{ title }}</text>
<switch class="switch" :checked="checked" color="#18bc37" @change="onChange"></switch> <switch class="switch" :checked="checked" color="#18bc37" @change="onChange"></switch>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: "switch-bar", name: "switch-bar",
props: { props: {
title: { title: {
type: String, type: String,
required: true required: true
},
checked: {
type: Boolean,
default: false
}
}, },
data() { checked: {
return { type: Boolean,
value: this.checked default: false
} }
}, },
methods: { data() {
onChange(e) { return {
this.value = true; value: this.checked
setTimeout(()=>{
this.value = false;
},100)
//this.value = false;
this.$emit('change', e);
}
} }
},
methods: {
onChange(e) {
this.value = true;
setTimeout(() => {
this.value = false;
}, 100)
//this.value = false;
this.$emit('change', e);
}
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.switch-bar { .switch-bar {
width: 100%; width: 100%;
height: 100rpx; height: 100rpx;
font-size: 34rpx; font-size: 34rpx;
color: black; color: black;
margin-top: 5rpx; margin-top: 5rpx;
background-color: white; background-color: white;
line-height: 100rpx; line-height: 100rpx;
display: flex; display: flex;
.title { .title {
flex: 1; flex: 1;
margin-left: 40rpx; margin-left: 40rpx;
} }
.switch { .switch {
margin-right: 40rpx; margin-right: 40rpx;
}
} }
}
</style> </style>

239
im-uniapp/components/chat-at-box/chat-at-box.vue

@ -4,12 +4,12 @@
<view class="chat-at-top"> <view class="chat-at-top">
<view class="chat-at-tip"> 选择要提醒的人</view> <view class="chat-at-tip"> 选择要提醒的人</view>
<button class="chat-at-btn" type="warn" size="mini" @click="onClean()">清空 </button> <button class="chat-at-btn" type="warn" size="mini" @click="onClean()">清空 </button>
<button class="chat-at-btn" type="primary" size="mini" @click="onOk()">确定({{atUserIds.length}}) <button class="chat-at-btn" type="primary" size="mini" @click="onOk()">确定({{ atUserIds.length }})
</button> </button>
</view> </view>
<scroll-view v-show="atUserIds.length>0" scroll-x="true" scroll-left="120"> <scroll-view v-show="atUserIds.length > 0" scroll-x="true" scroll-left="120">
<view class="at-user-items"> <view class="at-user-items">
<view v-for="m in showMembers" v-show="m.checked" class="at-user-item"> <view v-for="m in showMembers" v-show="m.checked" class="at-user-item" :key="m.userId">
<head-image :name="m.showNickName" :url="m.headImage" size="mini"></head-image> <head-image :name="m.showNickName" :url="m.headImage" size="mini"></head-image>
</view> </view>
</view> </view>
@ -19,15 +19,14 @@
</view> </view>
<view class="member-items"> <view class="member-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true"> <scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="m in showMembers" v-show="m.showNickName.includes(searchText)" <view v-for="m in showMembers" v-show="m.showNickName.includes(searchText)" :key="m.userId">
:key="m.userId"> <view class="member-item" :class="{ checked: m.checked }" @click="onSwitchChecked(m)">
<view class="member-item" :class="{checked: m.checked}" @click="onSwitchChecked(m)">
<head-image :name="m.showNickName" :online="m.online" :url="m.headImage" <head-image :name="m.showNickName" :online="m.online" :url="m.headImage"
size="small"></head-image> size="small"></head-image>
<view class="member-name">{{ m.showNickName}}</view> <view class="member-name">{{ m.showNickName }}</view>
<!-- <view class="member-checked">--> <!-- <view class="member-checked">-->
<!-- <radio :checked="m.checked" @click.stop="onSwitchChecked(m)" />--> <!-- <radio :checked="m.checked" @click.stop="onSwitchChecked(m)" />-->
<!-- </view>--> <!-- </view>-->
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
@ -37,143 +36,143 @@
</template> </template>
<script> <script>
export default { export default {
name: "chat-at-box", name: "chat-at-box",
props: { props: {
ownerId: { ownerId: {
type: Number, type: Number,
},
members: {
type: Array
}
},
data() {
return {
searchText: "",
showMembers:[]
};
}, },
methods: { members: {
init(atUserIds) { type: Array
this.showMembers = []; }
let userId = this.userStore.userInfo.id; },
if(this.ownerId == userId){ data() {
this.showMembers.push({ return {
userId:-1, searchText: "",
showNickName: "全体成员" showMembers: []
}) };
} },
this.members.forEach((m) => { methods: {
if(!m.quit && m.userId != userId){ init(atUserIds) {
m.checked = atUserIds.indexOf(m.userId) >= 0; this.showMembers = [];
this.showMembers.push(m); let userId = this.userStore.userInfo.id;
} if (this.ownerId == userId) {
}); this.showMembers.push({
}, userId: -1,
open() { showNickName: "全体成员"
this.$refs.popup.open();
},
onSwitchChecked(member) {
member.checked = !member.checked;
},
onClean() {
this.showMembers.forEach((m) => {
m.checked = false;
}) })
},
onOk() {
this.$refs.popup.close();
},
onChange(e) {
if (!e.show) {
this.$emit("complete", this.atUserIds)
}
} }
this.members.forEach((m) => {
if (!m.quit && m.userId != userId) {
m.checked = atUserIds.indexOf(m.userId) >= 0;
this.showMembers.push(m);
}
});
}, },
computed: { open() {
atUserIds() { this.$refs.popup.open();
let ids = []; },
this.showMembers.forEach((m) => { onSwitchChecked(member) {
if (m.checked) { member.checked = !member.checked;
ids.push(m.userId); },
} onClean() {
}) this.showMembers.forEach((m) => {
return ids; m.checked = false;
})
},
onOk() {
this.$refs.popup.close();
},
onChange(e) {
if (!e.show) {
this.$emit("complete", this.atUserIds)
} }
} }
},
computed: {
atUserIds() {
let ids = [];
this.showMembers.forEach((m) => {
if (m.checked) {
ids.push(m.userId);
}
})
return ids;
}
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat-at-box { .chat-at-box {
position: relative; position: relative;
display: flex;
flex-direction: column;
background-color: white;
padding: 10rpx;
//border-radius: 15rpx;
.chat-at-top {
display: flex; display: flex;
flex-direction: column; align-items: center;
background-color: white; height: 70rpx;
padding: 10rpx; padding: 10rpx;
//border-radius: 15rpx;
.chat-at-top {
display: flex;
align-items: center;
height: 70rpx;
padding: 10rpx;
.chat-at-tip { .chat-at-tip {
flex: 1; flex: 1;
} }
.chat-at-btn { .chat-at-btn {
margin-left: 10rpx; margin-left: 10rpx;
}
} }
}
.at-user-items { .at-user-items {
display: flex; display: flex;
align-items: center; align-items: center;
height: 90rpx; height: 90rpx;
.at-user-item { .at-user-item {
padding: 3rpx; padding: 3rpx;
}
} }
}
.member-items { .member-items {
position: relative;
flex: 1;
overflow: hidden;
.member-item {
height: 110rpx;
display: flex;
position: relative; position: relative;
flex: 1; padding: 0 30rpx;
overflow: hidden; align-items: center;
background-color: white;
.member-item { white-space: nowrap;
height: 110rpx; margin-bottom: 1px;
display: flex;
position: relative; &.checked {
padding: 0 30rpx; background-color: $im-color-primary-light-9;
align-items: center;
background-color: white;
white-space: nowrap;
margin-bottom: 1px;
&.checked {
background-color: $im-color-primary-light-9;
}
.member-name {
flex: 1;
padding-left: 20rpx;
font-size: $im-font-size;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
}
} }
.scroll-bar { .member-name {
height: 800rpx; flex: 1;
padding-left: 20rpx;
font-size: $im-font-size;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
} }
} }
.scroll-bar {
height: 800rpx;
}
} }
}
</style> </style>

173
im-uniapp/components/chat-group-readed/chat-group-readed.vue

@ -2,7 +2,8 @@
<uni-popup ref="popup" type="bottom"> <uni-popup ref="popup" type="bottom">
<view class="chat-group-readed"> <view class="chat-group-readed">
<view class="uni-padding-wrap uni-common-mt"> <view class="uni-padding-wrap uni-common-mt">
<uni-segmented-control :current="current" :values="items" style-type="button" @clickItem="onClickItem"/> <uni-segmented-control :current="current" :values="items" style-type="button"
@clickItem="onClickItem" />
</view> </view>
<view class="content"> <view class="content">
<view v-if="current === 0"> <view v-if="current === 0">
@ -11,7 +12,7 @@
<view class="member-item"> <view class="member-item">
<head-image :name="m.aliasName" :online="m.online" :url="m.headImage" <head-image :name="m.aliasName" :online="m.online" :url="m.headImage"
:size="90"></head-image> :size="90"></head-image>
<view class="member-name">{{ m.aliasName}}</view> <view class="member-name">{{ m.aliasName }}</view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
@ -22,7 +23,7 @@
<view class="member-item"> <view class="member-item">
<head-image :name="m.aliasName" :online="m.online" :url="m.headImage" <head-image :name="m.aliasName" :online="m.online" :url="m.headImage"
:size="90"></head-image> :size="90"></head-image>
<view class="member-name">{{ m.aliasName}}</view> <view class="member-name">{{ m.aliasName }}</view>
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
@ -33,99 +34,99 @@
</template> </template>
<script> <script>
export default { export default {
name: "chat-group-readed", name: "chat-group-readed",
data() { data() {
return { return {
items: ['已读', '未读'], items: ['已读', '未读'],
current: 0, current: 0,
readedMembers: [], readedMembers: [],
unreadMembers: [] unreadMembers: []
}; };
},
props: {
msgInfo: {
type: Object,
required: true
}, },
props: { groupMembers: {
msgInfo: { type: Array
type: Object, }
required: true },
}, methods: {
groupMembers: { open() {
type: Array this.$refs.popup.open();
} this.loadReadedUser();
}, },
methods: { loadReadedUser() {
open() { this.readedMembers = [];
this.$refs.popup.open(); this.unreadMembers = [];
this.loadReadedUser(); this.$http({
}, url: `/message/group/findReadedUsers?groupId=${this.msgInfo.groupId}&messageId=${this.msgInfo.id}`,
loadReadedUser() { method: 'Get'
this.readedMembers = []; }).then(userIds => {
this.unreadMembers = []; this.groupMembers.forEach(member => {
this.$http({ // 退
url: `/message/group/findReadedUsers?groupId=${this.msgInfo.groupId}&messageId=${this.msgInfo.id}`, if (member.userId == this.msgInfo.sendId || member.quit) {
method: 'Get' return;
}).then(userIds => { }
this.groupMembers.forEach(member => { //
// 退 if (userIds.find(userId => member.userId == userId)) {
if (member.userId == this.msgInfo.sendId || member.quit) { this.readedMembers.push(member);
return; } else {
} this.unreadMembers.push(member);
// }
if (userIds.find(userId => member.userId == userId)) { })
this.readedMembers.push(member); this.items[0] = `已读(${this.readedMembers.length})`;
} else { this.items[1] = `未读(${this.unreadMembers.length})`;
this.unreadMembers.push(member); //
} this.chatStore.updateMessage({
}) id: this.msgInfo.id,
this.items[0] = `已读(${this.readedMembers.length})`; groupId: this.msgInfo.groupId,
this.items[1] = `未读(${this.unreadMembers.length})`; readedCount: this.readedMembers.length
//
this.chatStore.updateMessage({
id: this.msgInfo.id,
groupId: this.msgInfo.groupId,
readedCount: this.readedMembers.length
})
}) })
}, })
onClickItem(e){ },
this.current = e.currentIndex; onClickItem(e) {
} this.current = e.currentIndex;
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat-group-readed { .chat-group-readed {
position: relative; position: relative;
display: flex;
flex-direction: column;
background-color: white;
padding: 10rpx;
//border-radius: 15rpx;
.scroll-bar {
height: 800rpx;
}
.member-item {
height: 120rpx;
display: flex; display: flex;
flex-direction: column; position: relative;
padding: 0 30rpx;
align-items: center;
background-color: white; background-color: white;
padding: 10rpx; white-space: nowrap;
//border-radius: 15rpx; .member-name {
.scroll-bar { flex: 1;
height: 800rpx; padding-left: 20rpx;
} font-size: 30rpx;
font-weight: 600;
.member-item { line-height: 60rpx;
height: 120rpx;
display: flex;
position: relative;
padding: 0 30rpx;
align-items: center;
background-color: white;
white-space: nowrap; white-space: nowrap;
overflow: hidden;
.member-name {
flex: 1;
padding-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
}
} }
} }
}
</style> </style>

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

@ -1,5 +1,5 @@
<template> <template>
<view class="chat-item" :class="active?'active':''"> <view class="chat-item" :class="active ? 'active' : ''">
<!--rich-text中的表情包会屏蔽事件所以这里用一个遮罩层捕获点击事件 --> <!--rich-text中的表情包会屏蔽事件所以这里用一个遮罩层捕获点击事件 -->
<view class="mask" @tap="showChatBox()"></view> <view class="mask" @tap="showChatBox()"></view>
<view class="left"> <view class="left">
@ -8,174 +8,175 @@
<view class="chat-right"> <view class="chat-right">
<view class="chat-name"> <view class="chat-name">
<view class="chat-name-text"> <view class="chat-name-text">
<view>{{chat.showName}}</view> <view>{{ chat.showName }}</view>
<uni-tag v-if="chat.type=='GROUP'" circle text="群" size="small" type="primary"></uni-tag> <uni-tag v-if="chat.type == 'GROUP'" circle text="群" size="small" type="primary"></uni-tag>
</view> </view>
<view class="chat-time">{{$date.toTimeText(chat.lastSendTime,true)}}</view> <view class="chat-time">{{ $date.toTimeText(chat.lastSendTime, true) }}</view>
</view> </view>
<view class="chat-content"> <view class="chat-content">
<view class="chat-at-text">{{atText}}</view> <view class="chat-at-text">{{ atText }}</view>
<view class="chat-send-name" v-if="isShowSendName">{{chat.sendNickName+':&nbsp;'}}</view> <view class="chat-send-name" v-if="isShowSendName">{{ chat.sendNickName + ':&nbsp;' }}</view>
<rich-text class="chat-content-text" :nodes="$emo.transform(chat.lastContent)"></rich-text> <rich-text class="chat-content-text" :nodes="$emo.transform(chat.lastContent)"></rich-text>
<uni-badge v-if="chat.unreadCount>0" :max-num="99" :text="chat.unreadCount" /> <uni-badge v-if="chat.unreadCount > 0" :max-num="99" :text="chat.unreadCount" />
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: "chatItem", name: "chatItem",
data() { data() {
return {} return {}
},
props: {
chat: {
type: Object
}, },
props: { index: {
chat: { type: Number
type: Object
},
index: {
type: Number
},
active: {
type: Boolean,
default: false
}
}, },
methods: { active: {
type: Boolean,
default: false
}
},
methods: {
showChatBox() { showChatBox() {
uni.navigateTo({ uni.navigateTo({
url: "/pages/chat/chat-box?chatIdx=" + this.index url: "/pages/chat/chat-box?chatIdx=" + this.index
}) })
}
},
computed: {
isShowSendName() {
if (!this.chat.sendNickName) {
return false;
} }
let size = this.chat.messages.length;
if (size == 0) {
return false;
}
//
let lastMsg = this.chat.messages[size - 1];
return this.$msgType.isNormal(lastMsg.type)
}, },
computed: { atText() {
isShowSendName() { if (this.chat.atMe) {
if (!this.chat.sendNickName) { return "[有人@我]"
return false; } else if (this.chat.atAll) {
} return "[@全体成员]"
let size = this.chat.messages.length;
if (size == 0) {
return false;
}
//
let lastMsg = this.chat.messages[size - 1];
return this.$msgType.isNormal(lastMsg.type)
},
atText() {
if (this.chat.atMe) {
return "[有人@我]"
} else if (this.chat.atAll) {
return "[@全体成员]"
}
return "";
} }
return "";
} }
} }
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.chat-item { .chat-item {
height: 96rpx; height: 96rpx;
display: flex; display: flex;
margin-bottom: 2rpx; margin-bottom: 2rpx;
position: relative; position: relative;
padding: 18rpx 20rpx; padding: 18rpx 20rpx;
align-items: center; align-items: center;
background-color: white; background-color: white;
white-space: nowrap; white-space: nowrap;
&:hover {
background-color: $im-bg-active;
}
&:hover { &.active {
background-color: $im-bg-active; background-color: $im-bg-active;
} }
&.active { .mask {
background-color: $im-bg-active; position: absolute;
} width: 100%;
height: 100%;
left: 0;
right: 0;
z-index: 99;
}
.mask { .left {
position: absolute; position: relative;
width: 100%; display: flex;
height: 100%; justify-content: center;
left: 0; align-items: center;
right: 0; width: 100rpx;
z-index: 99; height: 100rpx;
} }
.left { .chat-right {
position: relative; height: 100%;
display: flex; flex: 1;
justify-content: center; display: flex;
align-items: center; flex-direction: column;
width: 100rpx; justify-content: center;
height: 100rpx; padding-left: 20rpx;
} text-align: left;
overflow: hidden;
.chat-right { .chat-name {
height: 100%;
flex: 1;
display: flex; display: flex;
flex-direction: column;
justify-content: center;
padding-left: 20rpx;
text-align: left;
overflow: hidden;
.chat-name { .chat-name-text {
flex: 1;
font-size: $im-font-size-large;
white-space: nowrap;
overflow: hidden;
display: flex; display: flex;
align-items: center;
.chat-name-text {
flex: 1; .uni-tag {
font-size: $im-font-size-large; text-align: center;
white-space: nowrap; margin-left: 5rpx;
overflow: hidden; border: 0;
display: flex; padding: 1px 5px;
align-items: center; //opacity: 0.8;
.uni-tag {
text-align: center;
margin-left: 5rpx;
border: 0;
padding: 1px 5px;
//opacity: 0.8;
}
} }
}
.chat-time { .chat-time {
font-size: $im-font-size-smaller-extra; font-size: $im-font-size-smaller-extra;
color: $im-text-color-lighter; color: $im-text-color-lighter;
text-align: right; text-align: right;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
}
} }
}
.chat-content { .chat-content {
display: flex; display: flex;
font-size: $im-font-size-smaller; font-size: $im-font-size-smaller;
color: $im-text-color-lighter; color: $im-text-color-lighter;
padding-top: 8rpx; padding-top: 8rpx;
.chat-at-text {
color: $im-color-danger;
}
.chat-send-name { .chat-at-text {
font-size: $im-font-size-smaller; color: $im-color-danger;
} }
.chat-content-text { .chat-send-name {
flex: 1; font-size: $im-font-size-smaller;
white-space: nowrap; }
overflow: hidden;
text-overflow: ellipsis;
img { .chat-content-text {
width: 40rpx !important; flex: 1;
height: 40rpx !important; white-space: nowrap;
} overflow: hidden;
} text-overflow: ellipsis;
img {
width: 40rpx !important;
height: 40rpx !important;
}
} }
} }
} }
}
</style> </style>

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

@ -1,26 +1,26 @@
<template> <template>
<view class="chat-msg-item"> <view class="chat-msg-item">
<view class="chat-msg-tip" <view class="chat-msg-tip"
v-if="msgInfo.type==$enums.MESSAGE_TYPE.RECALL||msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT"> v-if="msgInfo.type == $enums.MESSAGE_TYPE.RECALL || msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT">
{{msgInfo.content}} {{ msgInfo.content }}
</view> </view>
<view class="chat-msg-tip" v-if="msgInfo.type==$enums.MESSAGE_TYPE.TIP_TIME"> <view class="chat-msg-tip" v-if="msgInfo.type == $enums.MESSAGE_TYPE.TIP_TIME">
{{$date.toTimeText(msgInfo.sendTime)}} {{ $date.toTimeText(msgInfo.sendTime) }}
</view> </view>
<view class="chat-msg-normal" v-if="isNormal" :class="{'chat-msg-mine':msgInfo.selfSend}"> <view class="chat-msg-normal" v-if="isNormal" :class="{ 'chat-msg-mine': msgInfo.selfSend }">
<head-image class="avatar" @longpress.prevent="$emit('longPressHead')" :id="msgInfo.sendId" :url="headImage" <head-image class="avatar" @longpress.prevent="$emit('longPressHead')" :id="msgInfo.sendId" :url="headImage"
:name="showName" size="small"></head-image> :name="showName" size="small"></head-image>
<view class="chat-msg-content"> <view class="chat-msg-content">
<view v-if="msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top"> <view v-if="msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top">
<text>{{showName}}</text> <text>{{ showName }}</text>
</view> </view>
<view class="chat-msg-bottom"> <view class="chat-msg-bottom">
<view v-if="msgInfo.type==$enums.MESSAGE_TYPE.TEXT"> <view v-if="msgInfo.type == $enums.MESSAGE_TYPE.TEXT">
<pop-menu :items="menuItems" @select="onSelectMenu"> <pop-menu :items="menuItems" @select="onSelectMenu">
<rich-text class="chat-msg-text" :nodes="$emo.transform(msgInfo.content)"></rich-text> <rich-text class="chat-msg-text" :nodes="$emo.transform(msgInfo.content)"></rich-text>
</pop-menu> </pop-menu>
</view> </view>
<view class="chat-msg-image" v-if="msgInfo.type==$enums.MESSAGE_TYPE.IMAGE"> <view class="chat-msg-image" v-if="msgInfo.type == $enums.MESSAGE_TYPE.IMAGE">
<pop-menu :items="menuItems" @select="onSelectMenu"> <pop-menu :items="menuItems" @select="onSelectMenu">
<view class="img-load-box"> <view class="img-load-box">
<image class="send-image" mode="widthFix" :src="JSON.parse(msgInfo.content).thumbUrl" <image class="send-image" mode="widthFix" :src="JSON.parse(msgInfo.content).thumbUrl"
@ -32,13 +32,13 @@
<text title="发送失败" v-if="loadFail" @click="onSendFail" <text title="发送失败" v-if="loadFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></text> class="send-fail iconfont icon-warning-circle-fill"></text>
</view> </view>
<view class="chat-msg-file" v-if="msgInfo.type==$enums.MESSAGE_TYPE.FILE"> <view class="chat-msg-file" v-if="msgInfo.type == $enums.MESSAGE_TYPE.FILE">
<pop-menu :items="menuItems" @select="onSelectMenu"> <pop-menu :items="menuItems" @select="onSelectMenu">
<view class="chat-file-box"> <view class="chat-file-box">
<view class="chat-file-info"> <view class="chat-file-info">
<uni-link class="chat-file-name" :text="data.name" showUnderLine="true" <uni-link class="chat-file-name" :text="data.name" showUnderLine="true"
color="#007BFF" :href="data.url"></uni-link> color="#007BFF" :href="data.url"></uni-link>
<view class="chat-file-size">{{fileSize}}</view> <view class="chat-file-size">{{ fileSize }}</view>
</view> </view>
<view class="chat-file-icon iconfont icon-file"></view> <view class="chat-file-icon iconfont icon-file"></view>
<loading v-if="loading"></loading> <loading v-if="loading"></loading>
@ -47,32 +47,32 @@
<text title="发送失败" v-if="loadFail" @click="onSendFail" <text title="发送失败" v-if="loadFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></text> class="send-fail iconfont icon-warning-circle-fill"></text>
</view> </view>
<pop-menu v-if="msgInfo.type==$enums.MESSAGE_TYPE.AUDIO" :items="menuItems" @select="onSelectMenu"> <pop-menu v-if="msgInfo.type == $enums.MESSAGE_TYPE.AUDIO" :items="menuItems" @select="onSelectMenu">
<view class="chat-msg-audio chat-msg-text" @click="onPlayAudio()"> <view class="chat-msg-audio chat-msg-text" @click="onPlayAudio()">
<text class="iconfont icon-voice-play"></text> <text class="iconfont icon-voice-play"></text>
<text class="chat-audio-text">{{JSON.parse(msgInfo.content).duration+'"'}}</text> <text class="chat-audio-text">{{ JSON.parse(msgInfo.content).duration + '"' }}</text>
<text v-if="audioPlayState=='PAUSE'" class="iconfont icon-play"></text> <text v-if="audioPlayState == 'PAUSE'" class="iconfont icon-play"></text>
<text v-if="audioPlayState=='PLAYING'" class="iconfont icon-pause"></text> <text v-if="audioPlayState == 'PLAYING'" class="iconfont icon-pause"></text>
</view> </view>
</pop-menu> </pop-menu>
<pop-menu v-if="isAction" :items="menuItems" @select="onSelectMenu"> <pop-menu v-if="isAction" :items="menuItems" @select="onSelectMenu">
<view class="chat-realtime chat-msg-text" @click="$emit('call')"> <view class="chat-realtime chat-msg-text" @click="$emit('call')">
<text v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VOICE" <text v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VOICE"
class="iconfont icon-chat-voice"></text> class="iconfont icon-chat-voice"></text>
<text v-if="msgInfo.type==$enums.MESSAGE_TYPE.ACT_RT_VIDEO" <text v-if="msgInfo.type == $enums.MESSAGE_TYPE.ACT_RT_VIDEO"
class="iconfont icon-chat-video"></text> class="iconfont icon-chat-video"></text>
<text>{{msgInfo.content}}</text> <text>{{ msgInfo.content }}</text>
</view> </view>
</pop-menu> </pop-menu>
<view class="chat-msg-status" v-if="!isAction"> <view class="chat-msg-status" v-if="!isAction">
<text class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId <text class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status==$enums.MESSAGE_STATUS.READED">已读</text> && msgInfo.status == $enums.MESSAGE_STATUS.READED">已读</text>
<text class="chat-unread" v-show="msgInfo.selfSend && !msgInfo.groupId <text class="chat-unread" v-show="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status!=$enums.MESSAGE_STATUS.READED">未读</text> && msgInfo.status != $enums.MESSAGE_STATUS.READED">未读</text>
</view> </view>
<view class="chat-receipt" v-show="msgInfo.receipt" @click="onShowReadedBox"> <view class="chat-receipt" v-show="msgInfo.receipt" @click="onShowReadedBox">
<text v-if="msgInfo.receiptOk" class="tool-icon iconfont icon-ok"></text> <text v-if="msgInfo.receiptOk" class="tool-icon iconfont icon-ok"></text>
<text v-else>{{msgInfo.readedCount}}人已读</text> <text v-else>{{ msgInfo.readedCount }}人已读</text>
</view> </view>
</view> </view>
</view> </view>
@ -83,404 +83,405 @@
</template> </template>
<script> <script>
export default { export default {
name: "chat-message-item", name: "chat-message-item",
props: { props: {
headImage: { headImage: {
type: String, type: String,
required: true required: true
},
showName: {
type: String,
required: true
},
msgInfo: {
type: Object,
required: true
},
groupMembers: {
type: Array
}
}, },
data() { showName: {
return { type: String,
audioPlayState: 'STOP', required: true
innerAudioContext: null, },
menu: { msgInfo: {
show: false, type: Object,
style: "" required: true
} },
groupMembers: {
type: Array
}
},
data() {
return {
audioPlayState: 'STOP',
innerAudioContext: null,
menu: {
show: false,
style: ""
} }
}
},
methods: {
onSendFail() {
uni.showToast({
title: "该文件已发送失败,目前不支持自动重新发送,建议手动重新发送",
icon: "none"
})
}, },
methods: { onPlayAudio() {
onSendFail() { //
uni.showToast({ if (!this.innerAudioContext) {
title: "该文件已发送失败,目前不支持自动重新发送,建议手动重新发送", this.innerAudioContext = uni.createInnerAudioContext();
icon: "none" let url = JSON.parse(this.msgInfo.content).url;
}) this.innerAudioContext.src = url;
}, console.log(url);
onPlayAudio() { this.innerAudioContext.onEnded((e) => {
// console.log('停止')
if (!this.innerAudioContext) { this.audioPlayState = "STOP"
this.innerAudioContext = uni.createInnerAudioContext();
let url = JSON.parse(this.msgInfo.content).url;
this.innerAudioContext.src = url;
console.log(url);
this.innerAudioContext.onEnded((e) => {
console.log('停止')
this.audioPlayState = "STOP"
})
this.innerAudioContext.onError((e) => {
console.log("播放音频出错");
console.log(e)
});
}
if (this.audioPlayState == 'STOP') {
this.innerAudioContext.play();
this.audioPlayState = "PLAYING";
} else if (this.audioPlayState == 'PLAYING') {
this.innerAudioContext.pause();
this.audioPlayState = "PAUSE"
} else if (this.audioPlayState == 'PAUSE') {
this.innerAudioContext.play();
this.audioPlayState = "PLAYING"
}
},
onSelectMenu(item) {
this.$emit(item.key.toLowerCase(), this.msgInfo);
this.menu.show = false;
},
onShowFullImage() {
let imageUrl = JSON.parse(this.msgInfo.content).originUrl;
uni.previewImage({
urls: [imageUrl]
}) })
}, this.innerAudioContext.onError((e) => {
onShowReadedBox() { console.log("播放音频出错");
this.$refs.chatGroupReaded.open(); console.log(e)
});
}
if (this.audioPlayState == 'STOP') {
this.innerAudioContext.play();
this.audioPlayState = "PLAYING";
} else if (this.audioPlayState == 'PLAYING') {
this.innerAudioContext.pause();
this.audioPlayState = "PAUSE"
} else if (this.audioPlayState == 'PAUSE') {
this.innerAudioContext.play();
this.audioPlayState = "PLAYING"
} }
}, },
computed: { onSelectMenu(item) {
loading() { this.$emit(item.key.toLowerCase(), this.msgInfo);
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "loading"; this.menu.show = false;
}, },
loadFail() { onShowFullImage() {
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "fail"; let imageUrl = JSON.parse(this.msgInfo.content).originUrl;
}, uni.previewImage({
data() { urls: [imageUrl]
return JSON.parse(this.msgInfo.content) })
}, },
fileSize() { onShowReadedBox() {
let size = this.data.size; this.$refs.chatGroupReaded.open();
if (size > 1024 * 1024) { }
return Math.round(size / 1024 / 1024) + "M"; },
} computed: {
if (size > 1024) { loading() {
return Math.round(size / 1024) + "KB"; return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "loading";
} },
return size + "B"; loadFail() {
}, return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "fail";
menuItems() { },
let items = []; data() {
if (this.msgInfo.type == this.$enums.MESSAGE_TYPE.TEXT) { return JSON.parse(this.msgInfo.content)
items.push({ },
key: 'COPY', fileSize() {
name: '复制', let size = this.data.size;
icon: 'bars' if (size > 1024 * 1024) {
}); return Math.round(size / 1024 / 1024) + "M";
} }
if (this.msgInfo.selfSend && this.msgInfo.id > 0) { if (size > 1024) {
items.push({ return Math.round(size / 1024) + "KB";
key: 'RECALL', }
name: '撤回', return size + "B";
icon: 'refreshempty' },
}); menuItems() {
} let items = [];
if (this.msgInfo.type == this.$enums.MESSAGE_TYPE.TEXT) {
items.push({ items.push({
key: 'DELETE', key: 'COPY',
name: '删除', name: '复制',
icon: 'trash', icon: 'bars'
color: '#e64e4e'
}); });
if (this.msgInfo.type == this.$enums.MESSAGE_TYPE.FILE) {
items.push({
key: 'DOWNLOAD',
name: '下载并打开',
icon: 'download'
});
}
return items;
},
isAction() {
return this.$msgType.isAction(this.msgInfo.type);
},
isNormal() {
const type = this.msgInfo.type;
return this.$msgType.isNormal(type) || this.$msgType.isAction(type)
} }
if (this.msgInfo.selfSend && this.msgInfo.id > 0) {
items.push({
key: 'RECALL',
name: '撤回',
icon: 'refreshempty'
});
}
items.push({
key: 'DELETE',
name: '删除',
icon: 'trash',
color: '#e64e4e'
});
if (this.msgInfo.type == this.$enums.MESSAGE_TYPE.FILE) {
items.push({
key: 'DOWNLOAD',
name: '下载并打开',
icon: 'download'
});
}
return items;
},
isAction() {
return this.$msgType.isAction(this.msgInfo.type);
},
isNormal() {
const type = this.msgInfo.type;
return this.$msgType.isNormal(type) || this.$msgType.isAction(type)
} }
} }
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.chat-msg-item { .chat-msg-item {
padding: 2rpx 20rpx; padding: 2rpx 20rpx;
.chat-msg-tip { .chat-msg-tip {
line-height: 60rpx; line-height: 60rpx;
text-align: center; text-align: center;
color: $im-text-color-lighter; color: $im-text-color-lighter;
font-size: $im-font-size-smaller-extra; font-size: $im-font-size-smaller-extra;
padding: 10rpx; padding: 10rpx;
}
.chat-msg-normal {
position: relative;
margin-bottom: 22rpx;
padding-left: 110rpx;
min-height: 80rpx;
.avatar {
position: absolute;
top: 0;
left: 0;
} }
.chat-msg-normal { .chat-msg-content {
position: relative; text-align: left;
margin-bottom: 22rpx;
padding-left: 110rpx;
min-height: 80rpx;
.avatar { .chat-msg-top {
position: absolute; display: flex;
top: 0; flex-wrap: nowrap;
left: 0; color: $im-text-color-lighter;
font-size: $im-font-size-smaller;
line-height: $im-font-size-smaller;
} }
.chat-msg-content { .chat-msg-bottom {
text-align: left; display: inline-block;
padding-right: 80rpx;
.chat-msg-text {
position: relative;
line-height: 1.6;
margin-top: 10rpx;
padding: 16rpx 24rpx;
background-color: $im-bg;
border-radius: 20rpx;
color: $im-text-color;
font-size: $im-font-size;
text-align: left;
display: block;
word-break: break-all;
white-space: pre-line;
&:after {
content: "";
position: absolute;
left: -20rpx;
top: 26rpx;
width: 6rpx;
height: 6rpx;
border-style: solid dashed dashed;
border-color: $im-bg transparent transparent;
overflow: hidden;
border-width: 18rpx;
//box-shadow: $im-box-shadow-dark;
}
}
.chat-msg-top { .chat-msg-image {
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
color: $im-text-color-lighter; flex-direction: row;
font-size: $im-font-size-smaller; align-items: center;
line-height: $im-font-size-smaller;
}
.chat-msg-bottom {
display: inline-block;
padding-right: 80rpx;
.chat-msg-text { .img-load-box {
position: relative; position: relative;
line-height: 1.6;
margin-top: 10rpx;
padding: 16rpx 24rpx;
background-color: $im-bg;
border-radius: 20rpx;
color: $im-text-color;
font-size: $im-font-size;
text-align: left;
display: block;
word-break: break-all;
white-space: pre-line;
&:after { .send-image {
content: ""; min-width: 200rpx;
position: absolute; max-width: 420rpx;
left: -20rpx; cursor: pointer;
top: 26rpx; border-radius: 4px;
width: 6rpx;
height: 6rpx;
border-style: solid dashed dashed;
border-color: $im-bg transparent transparent;
overflow: hidden;
border-width: 18rpx;
//box-shadow: $im-box-shadow-dark;
} }
} }
.chat-msg-image { .send-fail {
color: $im-color-danger;
font-size: $im-font-size;
cursor: pointer;
margin: 0 20px;
}
}
.chat-msg-file {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
cursor: pointer;
.chat-file-box {
position: relative;
display: flex; display: flex;
flex-wrap: nowrap; flex-wrap: nowrap;
flex-direction: row;
align-items: center; align-items: center;
min-height: 60px;
.img-load-box { border-radius: 4px;
position: relative; padding: 10px 15px;
box-shadow: $im-box-shadow-dark;
.send-image {
min-width: 200rpx; .chat-file-info {
max-width: 420rpx; flex: 1;
cursor: pointer; height: 100%;
border-radius: 4px; text-align: left;
font-size: 14px;
width: 300rpx;
.chat-file-name {
font-weight: 600;
margin-bottom: 15px;
word-break: break-all;
} }
} }
.chat-file-icon {
.send-fail { font-size: 80rpx;
color: $im-color-danger; color: #d42e07;
font-size: $im-font-size;
cursor: pointer;
margin: 0 20px;
} }
} }
.chat-msg-file { .send-fail {
display: flex; color: #e60c0c;
flex-wrap: nowrap; font-size: 50rpx;
flex-direction: row;
align-items: center;
cursor: pointer; cursor: pointer;
margin: 0 20rpx;
}
.chat-file-box { }
position: relative;
display: flex;
flex-wrap: nowrap;
align-items: center;
min-height: 60px;
border-radius: 4px;
padding: 10px 15px;
box-shadow: $im-box-shadow-dark;
.chat-file-info {
flex: 1;
height: 100%;
text-align: left;
font-size: 14px;
width: 300rpx;
.chat-file-name {
font-weight: 600;
margin-bottom: 15px;
word-break: break-all;
}
}
.chat-file-icon {
font-size: 80rpx;
color: #d42e07;
}
}
.send-fail { .chat-msg-audio {
color: #e60c0c; display: flex;
font-size: 50rpx; align-items: center;
cursor: pointer;
margin: 0 20rpx;
}
.chat-audio-text {
padding-right: 8px;
} }
.chat-msg-audio { .icon-voice-play {
display: flex; font-size: 18px;
align-items: center; padding-right: 8px;
}
}
.chat-audio-text { .chat-realtime {
padding-right: 8px; display: flex;
} align-items: center;
.icon-voice-play { .iconfont {
font-size: 18px; font-size: 20px;
padding-right: 8px; padding-right: 8px;
}
} }
}
.chat-realtime { .chat-msg-status {
display: flex; line-height: $im-font-size-smaller-extra;
align-items: center; font-size: $im-font-size-smaller-extra;
padding-top: 2rpx;
.iconfont { .chat-readed {
font-size: 20px; display: block;
padding-right: 8px; padding-top: 2rpx;
} color: $im-text-color-lighter;
} }
.chat-msg-status { .chat-unread {
line-height: $im-font-size-smaller-extra; color: $im-color-danger;
font-size: $im-font-size-smaller-extra;
padding-top: 2rpx;
.chat-readed {
display: block;
padding-top: 2rpx;
color: $im-text-color-lighter;
}
.chat-unread {
color: $im-color-danger;
}
} }
}
.chat-receipt { .chat-receipt {
font-size: $im-font-size-smaller; font-size: $im-font-size-smaller;
color: $im-text-color-lighter; color: $im-text-color-lighter;
font-weight: 600; font-weight: 600;
.icon-ok { .icon-ok {
font-size: 20px; font-size: 20px;
color: $im-color-success; color: $im-color-success;
}
} }
} }
} }
}
&.chat-msg-mine { &.chat-msg-mine {
text-align: right; text-align: right;
padding-left: 0; padding-left: 0;
padding-right: 110rpx; padding-right: 110rpx;
.avatar { .avatar {
left: auto; left: auto;
right: 0; right: 0;
} }
.chat-msg-content { .chat-msg-content {
text-align: right; text-align: right;
.chat-msg-bottom { .chat-msg-bottom {
padding-left: 80rpx; padding-left: 80rpx;
padding-right: 0; padding-right: 0;
.chat-msg-text { .chat-msg-text {
margin-left: 10px; margin-left: 10px;
background-color: $im-color-primary-light-2; background-color: $im-color-primary-light-2;
color: #fff; color: #fff;
&:after { &:after {
left: auto; left: auto;
right: -9px; right: -9px;
border-top-color: $im-color-primary-light-2; border-top-color: $im-color-primary-light-2;
}
} }
}
.chat-msg-image { .chat-msg-image {
flex-direction: row-reverse; flex-direction: row-reverse;
} }
.chat-msg-file { .chat-msg-file {
flex-direction: row-reverse; flex-direction: row-reverse;
} }
.chat-msg-audio { .chat-msg-audio {
flex-direction: row-reverse; flex-direction: row-reverse;
.chat-audio-text { .chat-audio-text {
padding-right: 0; padding-right: 0;
padding-left: 8px; padding-left: 8px;
} }
.icon-voice-play { .icon-voice-play {
transform: rotateY(180deg); transform: rotateY(180deg);
}
} }
}
.chat-realtime { .chat-realtime {
display: flex; display: flex;
flex-direction: row-reverse; flex-direction: row-reverse;
.iconfont { .iconfont {
transform: rotateY(180deg); transform: rotateY(180deg);
}
} }
} }
} }
} }
} }
} }
}
</style> </style>

372
im-uniapp/components/chat-record/chat-record.vue

@ -1,8 +1,8 @@
<template> <template>
<view class="chat-record"> <view class="chat-record">
<view class="chat-record-bar" :class="{recording: recording }" id="chat-record-bar" @click.stop="" <view class="chat-record-bar" :class="{ recording: recording }" id="chat-record-bar" @click.stop=""
@touchstart.prevent="onStartRecord" @touchmove.prevent="onTouchMove" @touchend.prevent="onEndRecord"> @touchstart.prevent="onStartRecord" @touchmove.prevent="onTouchMove" @touchend.prevent="onEndRecord">
{{recording?'正在录音':'长按 说话'}}</view> {{ recording ? '正在录音' : '长按 说话' }}</view>
<view v-if="recording" class="chat-record-window" :style="recordWindowStyle"> <view v-if="recording" class="chat-record-window" :style="recordWindowStyle">
<view class="rc-wave"> <view class="rc-wave">
<text class="note" style="--d: 0"></text> <text class="note" style="--d: 0"></text>
@ -13,226 +13,226 @@
<text class="note" style="--d: 5"></text> <text class="note" style="--d: 5"></text>
<text class="note" style="--d: 6"></text> <text class="note" style="--d: 6"></text>
</view> </view>
<view class="rc-tip">{{recordTip}}</view> <view class="rc-tip">{{ recordTip }}</view>
<view class="cancel-btn" @click="onCancel"> <view class="cancel-btn" @click="onCancel">
<uni-icons :class="moveToCancel?'red':'black'" type="clear" :size="moveToCancel?45:40"></uni-icons> <uni-icons :class="moveToCancel ? 'red' : 'black'" type="clear" :size="moveToCancel ? 45 : 40"></uni-icons>
</view> </view>
<view class="opt-tip" :class="moveToCancel?'red':'black'">{{moveToCancel? '松手取消':'松手发送,上划取消'}}</view> <view class="opt-tip" :class="moveToCancel ? 'red' : 'black'">{{ moveToCancel ? '松手取消' : '松手发送,上划取消' }}</view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: "chat-record", name: "chat-record",
data() { data() {
return { return {
recording: false, recording: false,
moveToCancel: false, moveToCancel: false,
recordBarTop: 0, recordBarTop: 0,
druation: 0, druation: 0,
rcTimer: null rcTimer: null
}
},
methods: {
onTouchMove(e) {
const moveY = e.touches[0].clientY;
this.moveToCancel = moveY < this.recordBarTop - 40;
},
onCancel() {
if (this.recording) {
this.moveToCancel = true;
this.onEndRecord();
} }
}, },
methods: { onStartRecord() {
onTouchMove(e) { /* 使@touchend
const moveY = e.touches[0].clientY; 一直处于录音状态这里允许用户再次点击发送语音并结束录音 */
this.moveToCancel = moveY < this.recordBarTop - 40; if (this.recording) {
}, this.onEndRecord();
onCancel() { return;
if (this.recording) { }
this.moveToCancel = true; console.log("开始录音")
this.onEndRecord(); this.moveToCancel = false;
} this.initRecordBar();
}, this.$rc.start().then(() => {
onStartRecord() { this.recording = true;
/* 使@touchend console.log("开始录音成功")
一直处于录音状态这里允许用户再次点击发送语音并结束录音 */ //
if (this.recording) { this.startTimer();
this.onEndRecord(); }).catch((e) => {
return; console.log("录音失败" + JSON.stringify(e))
} uni.showToast({
console.log("开始录音") title: "录音失败",
this.moveToCancel = false; icon: "none"
this.initRecordBar();
this.$rc.start().then(() => {
this.recording = true;
console.log("开始录音成功")
//
this.startTimer();
}).catch((e) => {
console.log("录音失败" + JSON.stringify(e))
uni.showToast({
title: "录音失败",
icon: "none"
});
}); });
}, });
onEndRecord() { },
this.recording = false; onEndRecord() {
// this.recording = false;
this.$rc.pause(); //
// this.$rc.pause();
this.StopTimer(); //
// this.StopTimer();
if (this.moveToCancel) { //
this.$rc.close(); if (this.moveToCancel) {
console.log("录音取消") this.$rc.close();
return; console.log("录音取消")
} return;
// 1 }
if (this.druation == 0) { // 1
uni.showToast({ if (this.druation == 0) {
title: "说话时间太短", uni.showToast({
icon: 'none' title: "说话时间太短",
}) icon: 'none'
this.$rc.close();
return;
}
this.$rc.upload().then((data) => {
this.$emit("send", data);
}).catch((e) => {
uni.showToast({
title: e,
icon: 'none'
})
}).finally(() => {
this.$rc.close();
}) })
}, this.$rc.close();
startTimer() { return;
this.druation = 0;
this.StopTimer();
this.rcTimer = setInterval(() => {
this.druation++;
// 60s,
if (this.druation >= 60) {
this.onEndRecord();
}
}, 1000)
},
StopTimer() {
this.rcTimer && clearInterval(this.rcTimer);
this.rcTimer = null;
},
initRecordBar() {
const query = uni.createSelectorQuery().in(this);
query.select('#chat-record-bar').boundingClientRect((rect) => {
//
this.recordBarTop = rect.top;
}).exec()
} }
this.$rc.upload().then((data) => {
this.$emit("send", data);
}).catch((e) => {
uni.showToast({
title: e,
icon: 'none'
})
}).finally(() => {
this.$rc.close();
})
}, },
computed: { startTimer() {
recordWindowStyle() { this.druation = 0;
const windowHeight = uni.getSystemInfoSync().windowHeight; this.StopTimer();
const bottom = windowHeight - this.recordBarTop + 12; this.rcTimer = setInterval(() => {
return `bottom:${bottom}px;` this.druation++;
}, // 60s,
recordTip() { if (this.druation >= 60) {
if (this.druation > 50) { this.onEndRecord();
return `${60-this.druation}s后将停止录音`;
} }
return `录音时长:${this.druation}s`; }, 1000)
},
StopTimer() {
this.rcTimer && clearInterval(this.rcTimer);
this.rcTimer = null;
},
initRecordBar() {
const query = uni.createSelectorQuery().in(this);
query.select('#chat-record-bar').boundingClientRect((rect) => {
//
this.recordBarTop = rect.top;
}).exec()
}
},
computed: {
recordWindowStyle() {
const windowHeight = uni.getSystemInfoSync().windowHeight;
const bottom = windowHeight - this.recordBarTop + 12;
return `bottom:${bottom}px;`
},
recordTip() {
if (this.druation > 50) {
return `${60 - this.druation}s后将停止录音`;
} }
return `录音时长:${this.druation}s`;
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat-record { .chat-record {
.rc-wave { .rc-wave {
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
justify-content: center; justify-content: center;
position: relative; position: relative;
height: 80rpx; height: 80rpx;
.note {
background: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
width: 4px;
height: 50%;
border-radius: 5rpx;
margin-right: 4px;
animation: loading 0.5s infinite linear;
animation-delay: calc(0.1s * var(--d));
@keyframes loading {
0% {
background-image: linear-gradient(to right, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
height: 20%;
border-radius: 5rpx;
}
50% {
background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
height: 80%;
border-radius: 5rpx;
}
.note { 100% {
background: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%); background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
width: 4px; height: 20%;
height: 50%; border-radius: 5rpx;
border-radius: 5rpx;
margin-right: 4px;
animation: loading 0.5s infinite linear;
animation-delay: calc(0.1s * var(--d));
@keyframes loading {
0% {
background-image: linear-gradient(to right, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
height: 20%;
border-radius: 5rpx;
}
50% {
background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
height: 80%;
border-radius: 5rpx;
}
100% {
background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
height: 20%;
border-radius: 5rpx;
}
} }
} }
} }
}
.chat-record-bar { .chat-record-bar {
padding: 10rpx; padding: 10rpx;
margin: 10rpx; margin: 10rpx;
border-radius: 10rpx; border-radius: 10rpx;
text-align: center; text-align: center;
box-shadow: $im-box-shadow; box-shadow: $im-box-shadow;
&.recording { &.recording {
background-color: $im-color-primary; background-color: $im-color-primary;
color: #fff; color: #fff;
}
} }
}
.chat-record-window { .chat-record-window {
position: fixed; position: fixed;
left: 0; left: 0;
right: 0; right: 0;
height: 360rpx; height: 360rpx;
background-color: rgba(255, 255, 255, 0.95); background-color: rgba(255, 255, 255, 0.95);
padding: 30rpx; padding: 30rpx;
.icon-microphone {
text-align: center;
font-size: 80rpx;
padding: 10rpx;
} .icon-microphone {
text-align: center;
font-size: 80rpx;
padding: 10rpx;
.rc-tip { }
text-align: center;
font-size: $im-font-size-small;
color: $im-text-color-light;
margin-top: 20rpx;
}
.cancel-btn { .rc-tip {
text-align: center; text-align: center;
margin-top: 40rpx; font-size: $im-font-size-small;
height: 80rpx; color: $im-text-color-light;
margin-top: 20rpx;
}
} .cancel-btn {
text-align: center;
margin-top: 40rpx;
height: 80rpx;
.opt-tip { }
text-align: center;
font-size: 30rpx;
padding: 20rpx;
}
.red { .opt-tip {
color: $im-color-danger !important; text-align: center;
} font-size: 30rpx;
padding: 20rpx;
}
.red {
color: $im-color-danger !important;
} }
} }
}
</style> </style>

187
im-uniapp/components/file-upload/file-upload.vue

@ -8,110 +8,109 @@
</template> </template>
<script> <script>
import UNI_APP from '@/.env.js'; import UNI_APP from '@/.env.js';
export default { export default {
name: "file-upload", name: "file-upload",
data() { data() {
return { return {
fileMap: new Map(), fileMap: new Map(),
option: { option: {
url: UNI_APP.BASE_URL + '/file/upload', url: UNI_APP.BASE_URL + '/file/upload',
name: 'file', name: 'file',
header: { header: {
accessToken: uni.getStorageSync('loginInfo').accessToken accessToken: uni.getStorageSync('loginInfo').accessToken
}
} }
} }
}
},
props: {
maxSize: {
type: Number,
default: 10
}, },
props: { onBefore: {
maxSize: { type: Function,
type: Number, default: null
default: 10
},
onBefore: {
type: Function,
default: null
},
onSuccess: {
type: Function,
default: null
},
onError: {
type: Function,
default: null
}
}, },
methods: { onSuccess: {
onUploadEnd(item) { type: Function,
let file = this.fileMap.get(item.path); default: null
if (item.type == 'fail') { },
this.onError(file) onError: {
return; type: Function,
} default: null
let res = JSON.parse(item.responseText); }
if (res.code == 200) { },
// methods: {
this.onOk(file, res); onUploadEnd(item) {
} else if (res.code == 401) { let file = this.fileMap.get(item.path);
// tokentoken if (item.type == 'fail') {
this.refreshToken().then((res) => { this.onError(file)
let newToken = res.data.accessToken; return;
this.option.header.accessToken = newToken; }
this.$refs.lsjUpload.setData(this.option); let res = JSON.parse(item.responseText);
// if (res.code == 200) {
this.$refs.lsjUpload.upload(file.name); //
}).catch(() => { this.onOk(file, res);
this.onError(file, res); } else if (res.code == 401) {
}) // tokentoken
} else { this.refreshToken().then((res) => {
// let newToken = res.data.accessToken;
this.option.header.accessToken = newToken;
this.$refs.lsjUpload.setData(this.option);
//
this.$refs.lsjUpload.upload(file.name);
}).catch(() => {
this.onError(file, res); this.onError(file, res);
})
} else {
//
this.onError(file, res);
}
},
onChange(files) {
if (!files.size) {
return;
}
files.forEach((file, name) => {
if (!this.fileMap.has(file.path)) {
this.onBefore && this.onBefore(file)
this.fileMap.set(file.path, file);
} }
}, })
onChange(files) { },
if (!files.size) { onOk(file, res) {
return; this.fileMap.delete(file.path);
} this.$refs.lsjUpload.clear(file.name);
files.forEach((file, name) => { this.onSuccess && this.onSuccess(file, res);
if(!this.fileMap.has(file.path)){ },
this.onBefore && this.onBefore(file) onFailed(file, res) {
this.fileMap.set(file.path, file); this.fileMap.delete(file.path);
this.$refs.lsjUpload.clear(file.name);
this.onError && this.onError(file, res);
},
refreshToken() {
return new Promise((resolve, reject) => {
let loginInfo = uni.getStorageSync('loginInfo')
uni.request({
method: 'PUT',
url: UNI_APP.BASE_URL + '/refreshToken',
header: {
refreshToken: loginInfo.refreshToken
},
success: (res) => {
resolve(res.data);
},
fail: (res) => {
reject(res);
} }
}) })
}, })
onOk(file, res) {
this.fileMap.delete(file.path);
this.$refs.lsjUpload.clear(file.name);
this.onSuccess && this.onSuccess(file, res);
},
onFailed(file, res) {
this.fileMap.delete(file.path);
this.$refs.lsjUpload.clear(file.name);
this.onError && this.onError(file, res);
},
refreshToken() {
return new Promise((resolve, reject) => {
let loginInfo = uni.getStorageSync('loginInfo')
uni.request({
method: 'PUT',
url: UNI_APP.BASE_URL + '/refreshToken',
header: {
refreshToken: loginInfo.refreshToken
},
success: (res) => {
resolve(res.data);
},
fail: (res) => {
reject(res);
}
})
})
}
} }
} }
}
</script> </script>
<style> <style></style>
</style>

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

@ -2,75 +2,74 @@
<view class="friend-item" @click="showFriendInfo()"> <view class="friend-item" @click="showFriendInfo()">
<head-image :name="friend.nickName" :online="friend.online" :url="friend.headImage" size="small"></head-image> <head-image :name="friend.nickName" :online="friend.online" :url="friend.headImage" size="small"></head-image>
<view class="friend-info"> <view class="friend-info">
<view class="friend-name">{{ friend.nickName}}</view> <view class="friend-name">{{ friend.nickName }}</view>
<view class="friend-online"> <view class="friend-online">
<image v-show="friend.onlineWeb" class="online" src="/static/image/online_web.png" <image v-show="friend.onlineWeb" class="online" src="/static/image/online_web.png" title="电脑设备在线" />
title="电脑设备在线" /> <image v-show="friend.onlineApp" class="online" src="/static/image/online_app.png" title="移动设备在线" />
<image v-show="friend.onlineApp" class="online" src="/static/image/online_app.png"
title="移动设备在线" />
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: "frined-item", name: "frined-item",
data() { data() {
return {} return {}
},
methods: {
showFriendInfo() {
uni.navigateTo({
url: "/pages/common/user-info?id=" + this.friend.id
})
}, },
methods: { },
showFriendInfo() { props: {
uni.navigateTo({ friend: {
url: "/pages/common/user-info?id=" + this.friend.id type: Object
})
},
},
props: {
friend: {
type: Object
}
} }
} }
}
</script> </script>
<style scope lang="scss"> <style scope lang="scss">
.friend-item { .friend-item {
height: 90rpx; height: 90rpx;
display: flex;
margin-bottom: 1rpx;
position: relative;
padding: 10rpx;
padding-left: 20rpx;
align-items: center;
background-color: white;
white-space: nowrap;
&:hover {
background-color: $im-bg;
}
.friend-info {
flex: 1;
display: flex; display: flex;
margin-bottom: 1rpx; flex-direction: column;
position: relative;
padding: 10rpx;
padding-left: 20rpx; padding-left: 20rpx;
align-items: center; text-align: left;
background-color: white;
white-space: nowrap;
&:hover { .friend-name {
background-color: $im-bg; font-size: $im-font-size;
white-space: nowrap;
overflow: hidden;
} }
.friend-info { .friend-online {
flex: 1; margin-top: 4rpx;
display: flex;
flex-direction: column;
padding-left: 20rpx;
text-align: left;
.friend-name {
font-size: $im-font-size;
white-space: nowrap;
overflow: hidden;
}
.friend-online { .online {
margin-top: 4rpx; padding-right: 4rpx;
.online { width: 24rpx;
padding-right: 4rpx; height: 24rpx;
width: 24rpx;
height: 24rpx;
}
} }
} }
} }
}
</style> </style>

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

@ -1,59 +1,58 @@
<template> <template>
<view class="group-item" @click="showGroupInfo()"> <view class="group-item" @click="showGroupInfo()">
<head-image :name="group.showGroupName" <head-image :name="group.showGroupName" :url="group.headImage" size="small"></head-image>
:url="group.headImage" size="small"></head-image>
<view class="group-name"> <view class="group-name">
<view>{{ group.showGroupName}}</view> <view>{{ group.showGroupName }}</view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: "group-item", name: "group-item",
data() { data() {
return {} return {}
},
methods: {
showGroupInfo() {
uni.navigateTo({
url: "/pages/group/group-info?id=" + this.group.id
})
}, },
methods:{ },
showGroupInfo(){ props: {
uni.navigateTo({ group: {
url:"/pages/group/group-info?id="+this.group.id type: Object
})
},
},
props: {
group: {
type: Object
}
} }
} }
}
</script> </script>
<style scope lang="scss"> <style scope lang="scss">
.group-item { .group-item {
height: 90rpx; height: 90rpx;
display: flex; display: flex;
margin-bottom: 2rpx; margin-bottom: 2rpx;
position: relative; position: relative;
padding: 18rpx 20rpx; padding: 18rpx 20rpx;
align-items: center; align-items: center;
background-color: white; background-color: white;
white-space: nowrap; white-space: nowrap;
&:hover { &:hover {
background-color: $im-bg-active; background-color: $im-bg-active;
} }
&.active { &.active {
background-color: $im-bg-active; background-color: $im-bg-active;
} }
.group-name { .group-name {
font-size: $im-font-size; font-size: $im-font-size;
padding-left: 20rpx; padding-left: 20rpx;
text-align: left; text-align: left;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
}
} }
}
</style> </style>

223
im-uniapp/components/group-member-selector/group-member-selector.vue

@ -4,12 +4,12 @@
<view class="top-bar"> <view class="top-bar">
<view class="top-tip">选择成员</view> <view class="top-tip">选择成员</view>
<button class="top-btn" type="warn" size="mini" @click="onClean()">清空 </button> <button class="top-btn" type="warn" size="mini" @click="onClean()">清空 </button>
<button class="top-btn" type="primary" size="mini" @click="onOk()">确定({{checkedIds.length}}) <button class="top-btn" type="primary" size="mini" @click="onOk()">确定({{ checkedIds.length }})
</button> </button>
</view> </view>
<scroll-view v-show="checkedIds.length>0" scroll-x="true" scroll-left="120"> <scroll-view v-show="checkedIds.length > 0" scroll-x="true" scroll-left="120">
<view class="checked-users"> <view class="checked-users">
<view v-for="m in members" v-show="m.checked" class="user-item"> <view v-for="m in members" v-show="m.checked" class="user-item" :key="m.userId">
<head-image :name="m.showNickName" :url="m.headImage" :size="60"></head-image> <head-image :name="m.showNickName" :url="m.headImage" :size="60"></head-image>
</view> </view>
</view> </view>
@ -23,7 +23,7 @@
<view class="member-item" @click="onSwitchChecked(m)"> <view class="member-item" @click="onSwitchChecked(m)">
<head-image :name="m.showNickName" :online="m.online" :url="m.headImage" <head-image :name="m.showNickName" :online="m.online" :url="m.headImage"
:size="90"></head-image> :size="90"></head-image>
<view class="member-name">{{ m.showNickName}}</view> <view class="member-name">{{ m.showNickName }}</view>
<view class="member-checked"> <view class="member-checked">
<radio :checked="m.checked" :disabled="m.locked" @click.stop="onSwitchChecked(m)" /> <radio :checked="m.checked" :disabled="m.locked" @click.stop="onSwitchChecked(m)" />
</view> </view>
@ -37,137 +37,138 @@
<script> <script>
export default { export default {
name: "chat-group-member-choose", name: "chat-group-member-choose",
props: { props: {
members: { members: {
type: Array type: Array
}, },
maxSize: { maxSize: {
type: Number, type: Number,
default: -1 default: -1
} }
},
data() {
return {
searchText: "",
};
},
methods: {
init(checkedIds, lockedIds) {
this.members.forEach((m) => {
m.checked = checkedIds.indexOf(m.userId) >= 0;
m.locked = lockedIds.indexOf(m.userId) >= 0;
});
}, },
data() { open() {
return { this.$refs.popup.open();
searchText: "",
};
}, },
methods: { onSwitchChecked(m) {
init(checkedIds, lockedIds) { if (!m.locked) {
this.members.forEach((m) => { m.checked = !m.checked;
m.checked = checkedIds.indexOf(m.userId) >= 0; }
m.locked = lockedIds.indexOf(m.userId) >= 0; //
}); if (this.maxSize > 0 && this.checkedIds.length > this.maxSize) {
}, m.checked = false;
open() { uni.showToast({
this.$refs.popup.open(); title: `最多选择${this.maxSize}位用户`,
}, icon: "none"
onSwitchChecked(m) { })
}
},
onClean() {
this.members.forEach((m) => {
if (!m.locked) { if (!m.locked) {
m.checked = !m.checked;
}
//
if (this.maxSize > 0 && this.checkedIds.length > this.maxSize) {
m.checked = false; m.checked = false;
uni.showToast({
title: `最多选择${this.maxSize}位用户`,
icon: "none"
})
} }
}, })
onClean() {
this.members.forEach((m) => {
if (!m.locked) {
m.checked = false;
}
})
},
onOk() {
this.$refs.popup.close();
this.$emit("complete", this.checkedIds)
},
isChecked(m) {
return this.checkedIds.indexOf(m.userId) >= 0;
}
}, },
computed: { onOk() {
checkedIds() { this.$refs.popup.close();
let ids = []; this.$emit("complete", this.checkedIds)
this.members.forEach((m) => { },
if (m.checked) { isChecked(m) {
ids.push(m.userId); return this.checkedIds.indexOf(m.userId) >= 0;
} }
}) },
return ids; computed: {
} checkedIds() {
let ids = [];
this.members.forEach((m) => {
if (m.checked) {
ids.push(m.userId);
}
})
return ids;
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat-group-member-choose { .chat-group-member-choose {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background-color: white; background-color: white;
padding: 10rpx; padding: 10rpx;
border-radius: 15rpx; border-radius: 15rpx;
.top-bar { .top-bar {
display: flex; display: flex;
align-items: center; align-items: center;
height: 70rpx; height: 70rpx;
padding: 10rpx 30rpx; padding: 10rpx 30rpx;
.top-tip { .top-tip {
flex: 1; flex: 1;
} }
.top-btn { .top-btn {
margin-left: 10rpx; margin-left: 10rpx;
}
} }
}
.checked-users { .checked-users {
display: flex; display: flex;
align-items: center; align-items: center;
height: 90rpx; height: 90rpx;
padding: 0 30rpx; padding: 0 30rpx;
.user-item {
padding: 3rpx; .user-item {
} padding: 3rpx;
} }
}
.member-items { .member-items {
position: relative;
flex: 1;
overflow: hidden;
.member-item {
height: 120rpx;
display: flex;
position: relative; position: relative;
flex: 1; padding: 0 30rpx;
overflow: hidden; align-items: center;
background-color: white;
white-space: nowrap;
.member-item { .member-name {
height: 120rpx; flex: 1;
display: flex; padding-left: 20rpx;
position: relative; font-size: 30rpx;
padding: 0 30rpx; font-weight: 600;
align-items: center; line-height: 60rpx;
background-color: white;
white-space: nowrap; white-space: nowrap;
overflow: hidden;
.member-name {
flex: 1;
padding-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
}
} }
}
.scroll-bar { .scroll-bar {
height: 800rpx; height: 800rpx;
}
} }
} }
}
</style> </style>

105
im-uniapp/components/group-rtc-join/group-rtc-join.vue

@ -1,17 +1,16 @@
<template> <template>
<uni-popup ref="popup" type="center"> <uni-popup ref="popup" type="center">
<uni-popup-dialog mode="base" :duration="2000" title="是否加入通话?" confirmText="加入" <uni-popup-dialog mode="base" :duration="2000" title="是否加入通话?" confirmText="加入" @confirm="onOk">
@confirm="onOk">
<div class="group-rtc-join"> <div class="group-rtc-join">
<div class="host-info"> <div class="host-info">
<div>发起人</div> <div>发起人</div>
<head-image :name="rtcInfo.host.nickName" :url="rtcInfo.host.headImage" :size="80"></head-image> <head-image :name="rtcInfo.host.nickName" :url="rtcInfo.host.headImage" :size="80"></head-image>
</div> </div>
<div class="user-info"> <div class="user-info">
<div>{{rtcInfo.userInfos.length+'人正在通话中'}}</div> <div>{{ rtcInfo.userInfos.length + '人正在通话中' }}</div>
<scroll-view scroll-x="true" scroll-left="120"> <scroll-view scroll-x="true" scroll-left="120">
<view class="user-list"> <view class="user-list">
<view v-for="user in rtcInfo.userInfos" class="user-item"> <view v-for="user in rtcInfo.userInfos" class="user-item" :key="user.id">
<head-image :name="user.nickName" :url="user.headImage" :size="80"></head-image> <head-image :name="user.nickName" :url="user.headImage" :size="80"></head-image>
</view> </view>
</view> </view>
@ -23,67 +22,67 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
rtcInfo: {} rtcInfo: {}
} }
}, },
props: { props: {
groupId: { groupId: {
type: Number type: Number
} }
},
methods: {
open(rtcInfo) {
this.rtcInfo = rtcInfo;
this.$refs.popup.open();
}, },
methods: { onOk() {
open(rtcInfo) { let users = this.rtcInfo.userInfos;
this.rtcInfo = rtcInfo; let mine = this.userStore.userInfo;
this.$refs.popup.open(); //
}, if (!users.find((user) => user.id == mine.id)) {
onOk() { users.push({
let users = this.rtcInfo.userInfos; id: mine.id,
let mine = this.userStore.userInfo; nickName: mine.nickName,
// headImage: mine.headImageThumb,
if(!users.find((user)=>user.id==mine.id)){ isCamera: false,
users.push({ isMicroPhone: true
id: mine.id,
nickName: mine.nickName,
headImage: mine.headImageThumb,
isCamera: false,
isMicroPhone: true
})
}
const userInfos = encodeURIComponent(JSON.stringify(users));
uni.navigateTo({
url: `/pages/chat/chat-group-video?groupId=${this.groupId}&isHost=false
&inviterId=${mine.id}&userInfos=${userInfos}`
}) })
} }
const userInfos = encodeURIComponent(JSON.stringify(users));
uni.navigateTo({
url: `/pages/chat/chat-group-video?groupId=${this.groupId}&isHost=false
&inviterId=${mine.id}&userInfos=${userInfos}`
})
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.group-rtc-join { .group-rtc-join {
width: 100%; width: 100%;
.host-info { .host-info {
font-size: 16px; font-size: 16px;
padding: 10px; padding: 10px;
} }
.user-info { .user-info {
font-size: 16px; font-size: 16px;
padding: 10px; padding: 10px;
} }
.user-list { .user-list {
display: flex; display: flex;
align-items: center; align-items: center;
height: 90rpx; height: 90rpx;
.user-item { .user-item {
padding: 3rpx; padding: 3rpx;
}
} }
} }
}
</style> </style>

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

@ -1,9 +1,9 @@
<template> <template>
<view class="head-image" @click="showUserInfo($event)" :title="name"> <view class="head-image" @click="showUserInfo($event)" :title="name">
<image class="avatar-image" v-if="url" :src="url" <image class="avatar-image" v-if="url" :src="url" :style="avatarImageStyle" lazy-load="true"
:style="avatarImageStyle" lazy-load="true" mode="aspectFill"/> mode="aspectFill" />
<view class="avatar-text" v-if="!url" :style="avatarTextStyle"> <view class="avatar-text" v-if="!url" :style="avatarTextStyle">
{{name?.substring(0,1).toUpperCase()}} {{ name?.substring(0, 1).toUpperCase() }}
</view> </view>
<view v-if="online" class="online" title="用户当前在线"> <view v-if="online" class="online" title="用户当前在线">
</view> </view>
@ -11,110 +11,110 @@
</template> </template>
<script> <script>
export default { export default {
name: "head-image", name: "head-image",
data() { data() {
return { return {
colors: ["#5daa31", "#c7515a", "#e03697", "#85029b", colors: ["#5daa31", "#c7515a", "#e03697", "#85029b",
"#c9b455", "#326eb6"] "#c9b455", "#326eb6"]
} }
},
props: {
id: {
type: Number
},
size: {
type: [Number, String],
default: 'default'
},
url: {
type: String
}, },
props: { name: {
id: { type: String,
type: Number default: null
},
size: {
type: [Number, String],
default: 'default'
},
url: {
type: String
},
name: {
type: String,
default: null
},
online: {
type: Boolean,
default: false
},
}, },
methods: { online: {
showUserInfo(e) { type: Boolean,
if (this.id && this.id > 0) { default: false
uni.navigateTo({ },
url: "/pages/common/user-info?id="+this.id },
}) methods: {
} showUserInfo(e) {
if (this.id && this.id > 0) {
uni.navigateTo({
url: "/pages/common/user-info?id=" + this.id
})
}
}
},
computed: {
_size() {
if (typeof this.size === 'number') {
return this.size;
} else if (typeof this.size === 'string') {
return {
'default': 96,
'small': 84,
'smaller': 72,
'mini': 60,
'minier': 48,
'lage': 108,
'lager': 120,
}[this.size]
} }
}, },
computed: { avatarImageStyle() {
_size(){ return `width:${this._size}rpx;
if(typeof this.size === 'number'){
return this.size;
} else if(typeof this.size === 'string'){
return {
'default': 96,
'small': 84,
'smaller': 72,
'mini': 60,
'minier': 48,
'lage': 108,
'lager': 120,
}[this.size]
}
},
avatarImageStyle() {
return `width:${this._size}rpx;
height:${this._size}rpx;` height:${this._size}rpx;`
}, },
avatarTextStyle() { avatarTextStyle() {
return `width: ${this._size}rpx; return `width: ${this._size}rpx;
height:${this._size}rpx; height:${this._size}rpx;
background-color:${this.name ? this.textColor : '#fff'}; background-color:${this.name ? this.textColor : '#fff'};
font-size:${this._size*0.5}rpx; font-size:${this._size * 0.5}rpx;
` `
}, },
textColor() { textColor() {
let hash = 0; let hash = 0;
for (var i = 0; i < this.name.length; i++) { for (var i = 0; i < this.name.length; i++) {
hash += this.name.charCodeAt(i); hash += this.name.charCodeAt(i);
}
return this.colors[hash % this.colors.length];
} }
return this.colors[hash % this.colors.length];
} }
} }
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.head-image { .head-image {
position: relative; position: relative;
cursor: pointer; cursor: pointer;
.avatar-image { .avatar-image {
position: relative; position: relative;
overflow: hidden; overflow: hidden;
border-radius: 50%; border-radius: 50%;
vertical-align: bottom; vertical-align: bottom;
} }
.avatar-text { .avatar-text {
color: white; color: white;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
} }
.online { .online {
position: absolute; position: absolute;
right: -10%; right: -10%;
bottom: 0; bottom: 0;
width: 24rpx; width: 24rpx;
height: 24rpx; height: 24rpx;
background: limegreen; background: limegreen;
border-radius: 50%; border-radius: 50%;
border: 6rpx solid white; border: 6rpx solid white;
}
} }
}
</style> </style>

151
im-uniapp/components/image-upload/image-upload.vue

@ -5,89 +5,88 @@
</template> </template>
<script> <script>
import UNI_APP from '@/.env.js' import UNI_APP from '@/.env.js'
export default { export default {
name: "image-upload", name: "image-upload",
data() { data() {
return { return {
uploadHeaders: { uploadHeaders: {
"accessToken": uni.getStorageSync('loginInfo').accessToken "accessToken": uni.getStorageSync('loginInfo').accessToken
}
} }
}
},
props: {
maxCount: {
type: Number,
default: 1
}, },
props: { maxSize: {
maxCount:{ type: Number,
type: Number, default: 5 * 1024 * 1024
default: 1
},
maxSize: {
type: Number,
default: 5*1024*1024
},
sourceType:{
type: String,
default: 'album'
},
onBefore: {
type: Function,
default: null
},
onSuccess: {
type: Function,
default: null
},
onError: {
type: Function,
default: null
}
}, },
methods: { sourceType: {
selectAndUpload() { type: String,
uni.chooseImage({ default: 'album'
count: this.maxCount, //9 },
sourceType: [this.sourceType], //album camera 使使 onBefore: {
sizeType: ['original'], //original compressed type: Function,
success: (res) => { default: null
res.tempFiles.forEach((file) => { },
console.log("文件:",file) onSuccess: {
if (!this.onBefore || this.onBefore(file)) { type: Function,
// default: null
this.uploadImage(file); },
} onError: {
}) type: Function,
} default: null
}) }
}, },
uploadImage(file) { methods: {
uni.uploadFile({ selectAndUpload() {
url: UNI_APP.BASE_URL + '/image/upload', uni.chooseImage({
header: { count: this.maxCount, //9
accessToken: uni.getStorageSync("loginInfo").accessToken sourceType: [this.sourceType], //album camera 使使
}, sizeType: ['original'], //original compressed
filePath: file.path, // success: (res) => {
name: 'file', res.tempFiles.forEach((file) => {
success: (res) => { console.log("文件:", file)
let data = JSON.parse(res.data); if (!this.onBefore || this.onBefore(file)) {
if(data.code != 200){ //
uni.showToast({ this.uploadImage(file);
icon: "none",
title: data.message,
})
this.onError && this.onError(file, data);
}else{
this.onSuccess && this.onSuccess(file, data);
} }
}, })
fail: (err) => { }
console.log(err); })
this.onError && this.onError(file, err); },
uploadImage(file) {
uni.uploadFile({
url: UNI_APP.BASE_URL + '/image/upload',
header: {
accessToken: uni.getStorageSync("loginInfo").accessToken
},
filePath: file.path, //
name: 'file',
success: (res) => {
let data = JSON.parse(res.data);
if (data.code != 200) {
uni.showToast({
icon: "none",
title: data.message,
})
this.onError && this.onError(file, data);
} else {
this.onSuccess && this.onSuccess(file, data);
} }
}) },
} fail: (err) => {
console.log(err);
this.onError && this.onError(file, err);
}
})
} }
} }
}
</script> </script>
<style> <style></style>
</style>

84
im-uniapp/components/loading/loading.vue

@ -6,59 +6,57 @@
</template> </template>
<script> <script>
import {
computed export default {
} from "vue" data() {
export default { return {}
data() { },
return {} props: {
size: {
type: Number,
default: 100
}, },
props: { mask: {
size: { type: Boolean,
type: Number, default: true
default: 100 }
}, },
mask: { computed: {
type: Boolean, icontStyle() {
default: true return `font-size:${this.size}rpx`;
}
}, },
computed: { loadingStyle() {
icontStyle() { return this.mask ? "background: rgba(0, 0, 0, 0.3);" : "";
return `font-size:${this.size}rpx`;
},
loadingStyle() {
return this.mask ? "background: rgba(0, 0, 0, 0.3);" : "";
}
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.loading-box { .loading-box {
width: 100%; width: 100%;
height: 100%; height: 100%;
position: absolute; position: absolute;
left: 0; left: 0;
top: 0; top: 0;
z-index: 10000; z-index: 10000;
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
} }
.rotate { .rotate {
animation: rotate 2s ease-in-out infinite; animation: rotate 2s ease-in-out infinite;
} }
@keyframes rotate { @keyframes rotate {
from { from {
transform: rotate(0deg) transform: rotate(0deg)
} }
to { to {
transform: rotate(360deg) transform: rotate(360deg)
}
} }
}
</style> </style>

9
im-uniapp/components/nav-bar/nav-bar.vue

@ -11,9 +11,11 @@
<slot></slot> <slot></slot>
</view> </view>
<view class="btn"> <view class="btn">
<uni-icons class="btn-item" v-if="search" type="search" :size="iconFontSize" @click="$emit('search')"></uni-icons> <uni-icons class="btn-item" v-if="search" type="search" :size="iconFontSize"
@click="$emit('search')"></uni-icons>
<uni-icons class="btn-item" v-if="add" type="plusempty" :size="iconFontSize" @click="$emit('add')"></uni-icons> <uni-icons class="btn-item" v-if="add" type="plusempty" :size="iconFontSize" @click="$emit('add')"></uni-icons>
<uni-icons class="btn-item" v-if="more" type="more-filled" :size="iconFontSize" @click="$emit('more')"></uni-icons> <uni-icons class="btn-item" v-if="more" type="more-filled" :size="iconFontSize"
@click="$emit('more')"></uni-icons>
</view> </view>
</view> </view>
</view> </view>
@ -85,8 +87,7 @@ export default {
box-sizing: border-box; box-sizing: border-box;
height: $im-nav-bar-height; height: $im-nav-bar-height;
.title { .title {}
}
.back { .back {
position: absolute; position: absolute;

188
im-uniapp/components/pop-menu/pop-menu.vue

@ -5,117 +5,117 @@
</view> </view>
<view v-if="isShowMenu" class="pop-menu" @touchstart="onClose()" @contextmenu.prevent=""></view> <view v-if="isShowMenu" class="pop-menu" @touchstart="onClose()" @contextmenu.prevent=""></view>
<view v-if="isShowMenu" class="menu" :style="menuStyle"> <view v-if="isShowMenu" class="menu" :style="menuStyle">
<view class="menu-item" v-for="(item) in items" :key="item.key" @click.prevent="onSelectMenu(item)"> <view class="menu-item" v-for="(item) in items" :key="item.key" @click.prevent="onSelectMenu(item)">
<!-- <uni-icons class="menu-icon" :type="item.icon" :style="itemStyle(item)" size="20"></uni-icons>--> <text :style="itemStyle(item)"> {{ item.name }}</text>
<text :style="itemStyle(item)"> {{item.name}}</text>
</view> </view>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
name: "pop-menu", name: "pop-menu",
data() { data() {
return { return {
isShowMenu : false, isShowMenu: false,
isTouchMove: false, isTouchMove: false,
style : "" style: ""
} }
}, },
props: { props: {
items: { items: {
type: Array type: Array
}
},
methods: {
onLongPress(e) {
if (this.isTouchMove) {
//
return;
} }
}, uni.getSystemInfo({
methods: { success: (res) => {
onLongPress(e){ let touches = e.touches[0];
if(this.isTouchMove){ let style = "";
// /* 因 非H5端不兼容 style 属性绑定 Object ,所以拼接字符 */
return; if (touches.clientY > (res.windowHeight / 2)) {
} style = `bottom:${res.windowHeight - touches.clientY}px;`;
uni.getSystemInfo({ } else {
success: (res) => { style = `top:${touches.clientY}px;`;
let touches = e.touches[0]; }
let style = ""; if (touches.clientX > (res.windowWidth / 2)) {
/* 因 非H5端不兼容 style 属性绑定 Object ,所以拼接字符 */ style += `right:${res.windowWidth - touches.clientX}px;`;
if (touches.clientY > (res.windowHeight / 2)) { } else {
style = `bottom:${res.windowHeight-touches.clientY}px;`; style += `left:${touches.clientX}px;`;
} else {
style = `top:${touches.clientY}px;`;
}
if (touches.clientX > (res.windowWidth / 2)) {
style += `right:${res.windowWidth-touches.clientX}px;`;
} else {
style += `left:${touches.clientX}px;`;
}
this.menuStyle = style;
//
this.$nextTick(() => {
this.isShowMenu = true;
});
} }
}) this.menuStyle = style;
}, //
onTouchMove(){ this.$nextTick(() => {
this.onClose(); this.isShowMenu = true;
this.isTouchMove = true; });
},
onTouchEnd(){
this.isTouchMove = false;
},
onSelectMenu(item) {
this.$emit("select", item);
this.isShowMenu = false;
},
onClose() {
this.isShowMenu = false;
},
itemStyle(item){
if(item.color){
return `color:${item.color};`
} }
// return `color:#4f76e6;`; })
},
onTouchMove() {
this.onClose();
this.isTouchMove = true;
},
onTouchEnd() {
this.isTouchMove = false;
},
onSelectMenu(item) {
this.$emit("select", item);
this.isShowMenu = false;
},
onClose() {
this.isShowMenu = false;
},
itemStyle(item) {
if (item.color) {
return `color:${item.color};`
} }
// return `color:#4f76e6;`;
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.pop-menu { .pop-menu {
position: fixed; position: fixed;
left: 0; left: 0;
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
height: 100%; height: 100%;
z-index: 999; z-index: 999;
} }
.menu {
position: fixed;
border-radius: 4px;
overflow: hidden;
background-color: #fff;
z-index: 1000;
box-shadow: $im-box-shadow-dark;
.menu-item {
height: 28px;
min-width: 120rpx;
line-height: 28px;
font-size: $im-font-size-small;
display: flex;
padding: 6px 20px;
justify-content: flex-start;
&:hover { .menu {
background: $im-bg-active; position: fixed;
} border-radius: 4px;
overflow: hidden;
background-color: #fff;
z-index: 1000;
box-shadow: $im-box-shadow-dark;
.menu-icon { .menu-item {
margin-right: 10rpx; height: 28px;
} min-width: 120rpx;
line-height: 28px;
font-size: $im-font-size-small;
display: flex;
padding: 6px 20px;
justify-content: flex-start;
&:hover {
background: $im-bg-active;
}
.menu-icon {
margin-right: 10rpx;
} }
} }
}
</style> </style>

17
im-uniapp/main.js

@ -27,9 +27,6 @@ import * as recorder from './common/recorder-h5';
// #ifndef H5 // #ifndef H5
import * as recorder from './common/recorder-app'; import * as recorder from './common/recorder-app';
// #endif // #endif
export function createApp() { export function createApp() {
const app = createSSRApp(App) const app = createSSRApp(App)
app.use(uviewPlus); app.use(uviewPlus);
@ -46,15 +43,15 @@ export function createApp() {
app.config.globalProperties.$date = date; app.config.globalProperties.$date = date;
app.config.globalProperties.$rc = recorder; app.config.globalProperties.$rc = recorder;
// 初始化时再挂载store对象 // 初始化时再挂载store对象
app.config.globalProperties.$mountStore = ()=>{ app.config.globalProperties.$mountStore = () => {
app.config.globalProperties.chatStore = useChatStore(); app.config.globalProperties.chatStore = useChatStore();
app.config.globalProperties.friendStore = useFriendStore(); app.config.globalProperties.friendStore = useFriendStore();
app.config.globalProperties.groupStore = useGroupStore(); app.config.globalProperties.groupStore = useGroupStore();
app.config.globalProperties.configStore = useConfigStore(); app.config.globalProperties.configStore = useConfigStore();
app.config.globalProperties.userStore = useUserStore(); app.config.globalProperties.userStore = useUserStore();
} }
return { return {
app, app,
pinia pinia
} }
} }

167
im-uniapp/manifest.json

@ -1,32 +1,32 @@
{ {
"name" : "盒子IM", "name": "盒子IM",
"appid" : "__UNI__69DD57A", "appid": "__UNI__69DD57A",
"description" : "", "description": "",
"versionName" : "3.1.0", "versionName": "3.1.0",
"versionCode" : 3100, "versionCode": 3100,
"transformPx" : false, "transformPx": false,
/* 5+App */ /* 5+App */
"app-plus" : { "app-plus": {
"usingComponents" : true, "usingComponents": true,
"nvueStyleCompiler" : "uni-app", "nvueStyleCompiler": "uni-app",
"compilerVersion" : 3, "compilerVersion": 3,
"splashscreen" : { "splashscreen": {
"alwaysShowBeforeRender" : true, "alwaysShowBeforeRender": true,
"waiting" : true, "waiting": true,
"autoclose" : true, "autoclose": true,
"delay" : 0 "delay": 0
}, },
/* */ /* */
"modules" : { "modules": {
"Camera" : {}, "Camera": {},
"Record" : {}, "Record": {},
"Bluetooth" : {} "Bluetooth": {}
}, },
/* */ /* */
"distribute" : { "distribute": {
/* android */ /* android */
"android" : { "android": {
"permissions" : [ "permissions": [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>", "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>", "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>", "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
@ -45,87 +45,90 @@
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />", "<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />" "<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />"
], ],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ], "abiFilters": [
"minSdkVersion" : 21 "armeabi-v7a",
"arm64-v8a",
"x86"
],
"minSdkVersion": 21
}, },
/* ios */ /* ios */
"ios" : { "ios": {
"dSYMs" : false, "dSYMs": false,
"privacyDescription" : { "privacyDescription": {
"NSMicrophoneUsageDescription" : "", "NSMicrophoneUsageDescription": "",
"NSCameraUsageDescription" : "" "NSCameraUsageDescription": ""
}, },
"idfa" : false "idfa": false
}, },
/* SDK */ /* SDK */
"sdkConfigs" : { "sdkConfigs": {
"ad" : {}, "ad": {},
"speech" : {} "speech": {}
}, },
"icons" : { "icons": {
"android" : { "android": {
"xhdpi" : "unpackage/res/icons/96x96.png", "xhdpi": "unpackage/res/icons/96x96.png",
"hdpi" : "unpackage/res/icons/72x72.png", "hdpi": "unpackage/res/icons/72x72.png",
"xxhdpi" : "unpackage/res/icons/144x144.png", "xxhdpi": "unpackage/res/icons/144x144.png",
"xxxhdpi" : "unpackage/res/icons/192x192.png" "xxxhdpi": "unpackage/res/icons/192x192.png"
}, },
"ios" : { "ios": {
"appstore" : "unpackage/res/icons/1024x1024.png", "appstore": "unpackage/res/icons/1024x1024.png",
"ipad" : { "ipad": {
"app" : "unpackage/res/icons/76x76.png", "app": "unpackage/res/icons/76x76.png",
"app@2x" : "unpackage/res/icons/152x152.png", "app@2x": "unpackage/res/icons/152x152.png",
"notification" : "unpackage/res/icons/20x20.png", "notification": "unpackage/res/icons/20x20.png",
"notification@2x" : "unpackage/res/icons/40x40.png", "notification@2x": "unpackage/res/icons/40x40.png",
"proapp@2x" : "unpackage/res/icons/167x167.png", "proapp@2x": "unpackage/res/icons/167x167.png",
"settings" : "unpackage/res/icons/29x29.png", "settings": "unpackage/res/icons/29x29.png",
"settings@2x" : "unpackage/res/icons/58x58.png", "settings@2x": "unpackage/res/icons/58x58.png",
"spotlight" : "unpackage/res/icons/40x40.png", "spotlight": "unpackage/res/icons/40x40.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png" "spotlight@2x": "unpackage/res/icons/80x80.png"
}, },
"iphone" : { "iphone": {
"app@2x" : "unpackage/res/icons/120x120.png", "app@2x": "unpackage/res/icons/120x120.png",
"app@3x" : "unpackage/res/icons/180x180.png", "app@3x": "unpackage/res/icons/180x180.png",
"notification@2x" : "unpackage/res/icons/40x40.png", "notification@2x": "unpackage/res/icons/40x40.png",
"notification@3x" : "unpackage/res/icons/60x60.png", "notification@3x": "unpackage/res/icons/60x60.png",
"settings@2x" : "unpackage/res/icons/58x58.png", "settings@2x": "unpackage/res/icons/58x58.png",
"settings@3x" : "unpackage/res/icons/87x87.png", "settings@3x": "unpackage/res/icons/87x87.png",
"spotlight@2x" : "unpackage/res/icons/80x80.png", "spotlight@2x": "unpackage/res/icons/80x80.png",
"spotlight@3x" : "unpackage/res/icons/120x120.png" "spotlight@3x": "unpackage/res/icons/120x120.png"
} }
} }
} }
} }
}, },
/* */ /* */
"quickapp" : {}, "quickapp": {},
/* */ /* */
"mp-weixin" : { "mp-weixin": {
"appid" : "wxda94f40bfad0262c", "appid": "wxda94f40bfad0262c",
"libVersion" : "latest", "libVersion": "latest",
"setting" : { "setting": {
"urlCheck" : false "urlCheck": false
}, },
"usingComponents" : true "usingComponents": true
}, },
"mp-alipay" : { "mp-alipay": {
"usingComponents" : true "usingComponents": true
}, },
"mp-baidu" : { "mp-baidu": {
"usingComponents" : true "usingComponents": true
}, },
"mp-toutiao" : { "mp-toutiao": {
"usingComponents" : true "usingComponents": true
}, },
"uniStatistics" : { "uniStatistics": {
"enable" : false "enable": false
}, },
"vueVersion" : "3", "vueVersion": "3",
"h5" : { "h5": {
"title" : "盒子IM", "title": "盒子IM",
"router" : { "router": {
"base" : "/h5/" "base": "/h5/"
} }
} }
} }
/* ios *//* SDK */ /* ios */ /* SDK */

2
im-uniapp/package.json

@ -8,4 +8,4 @@
"pinyin-pro": "^3.23.1", "pinyin-pro": "^3.23.1",
"vconsole": "^3.15.1" "vconsole": "^3.15.1"
} }
} }

103
im-uniapp/pages.json

@ -1,47 +1,64 @@
{ {
"easycom": { "easycom": {
"autoscan": true, "autoscan": true,
"custom": { "custom": {
"^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", "^u--(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
"^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue", "^up-(.*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue",
"^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue" "^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue"
} }
},
"pages": [
{
"path": "pages/login/login"
},
{
"path": "pages/register/register"
},
{
"path": "pages/chat/chat"
},
{
"path": "pages/friend/friend"
},
{
"path": "pages/group/group"
},
{
"path": "pages/mine/mine"
},
{
"path": "pages/common/user-info"
},
{
"path": "pages/chat/chat-box"
}, },
"pages": [{ {
"path": "pages/login/login" "path": "pages/chat/chat-private-video"
}, { },
"path" : "pages/register/register" {
},{ "path": "pages/chat/chat-group-video"
"path": "pages/chat/chat" },
}, { {
"path": "pages/friend/friend" "path": "pages/friend/friend-add"
}, { },
"path": "pages/group/group" {
}, { "path": "pages/group/group-info"
"path": "pages/mine/mine" },
}, { {
"path": "pages/common/user-info" "path": "pages/group/group-edit"
}, { },
"path": "pages/chat/chat-box" {
},{ "path": "pages/group/group-invite"
"path": "pages/chat/chat-private-video" },
},{ {
"path": "pages/chat/chat-group-video" "path": "pages/group/group-member"
}, { },
"path": "pages/friend/friend-add" {
}, { "path": "pages/mine/mine-edit"
"path": "pages/group/group-info" },
}, { {
"path": "pages/group/group-edit" "path": "pages/mine/mine-password"
}, { }
"path": "pages/group/group-invite"
}, {
"path": "pages/group/group-member"
}, {
"path": "pages/mine/mine-edit"
},{
"path": "pages/mine/mine-password"
}
], ],
"globalStyle": { "globalStyle": {
"navigationStyle": "custom", "navigationStyle": "custom",
@ -53,12 +70,12 @@
"selectedColor": "#587ff0", "selectedColor": "#587ff0",
"borderStyle": "black", "borderStyle": "black",
"backgroundColor": "#ffffff", "backgroundColor": "#ffffff",
"list": [{ "list": [
{
"pagePath": "pages/chat/chat", "pagePath": "pages/chat/chat",
"iconPath": "static/tarbar/chat.png", "iconPath": "static/tarbar/chat.png",
"selectedIconPath": "static/tarbar/chat_active.png", "selectedIconPath": "static/tarbar/chat_active.png",
"text": "消息" "text": "消息"
}, },
{ {
"pagePath": "pages/friend/friend", "pagePath": "pages/friend/friend",

1528
im-uniapp/pages/chat/chat-box.vue

File diff suppressed because it is too large

230
im-uniapp/pages/chat/chat-group-video.vue

@ -3,137 +3,137 @@
</template> </template>
<script> <script>
import UNI_APP from '@/.env.js' import UNI_APP from '@/.env.js'
export default { export default {
data() { data() {
return { return {
url: "", url: "",
wv: '', wv: '',
isHost: false, isHost: false,
groupId: null, groupId: null,
inviterId: null, inviterId: null,
userInfos: [] userInfos: []
} }
},
methods: {
onMessage(e) {
this.onWebviewMessage(e.detail.data[0]);
}, },
methods: { onInsertMessage(msgInfo) {
onMessage(e) {
this.onWebviewMessage(e.detail.data[0]);
},
onInsertMessage(msgInfo) {
},
onWebviewMessage(event) {
console.log("来自webview的消息:" + JSON.stringify(event))
switch (event.key) {
case "WV_READY":
this.initWebView();
break;
case "WV_CLOSE":
uni.navigateBack();
break;
case "INSERT_MESSAGE":
this.onInsertMessage(event.data);
break;
}
},
sendMessageToWebView(key, message) {
// webview100ms
if (!this.wv) {
setTimeout(() => this.sendMessageToWebView(key, message), 100)
return;
}
let event = {
key: key,
data: message
}
// #ifdef APP-PLUS
this.wv.evalJS(`onEvent('${encodeURIComponent(JSON.stringify(event))}')`)
// #endif
// #ifdef H5
this.wv.postMessage(event, '*');
// #endif
},
initWebView() {
// #ifdef APP-PLUS
// APPwebview
this.wv = this.$scope.$getAppWebview().children()[0]
// #endif
// #ifdef H5
// H5webviewiframe
this.wv = document.getElementById('chat-video-wv').contentWindow
// #endif
},
initUrl() {
this.url = "/hybrid/html/rtc-group/index.html?";
this.url += "baseUrl=" + UNI_APP.BASE_URL;
this.url += "&groupId=" + this.groupId;
this.url += "&userId=" + this.userStore.userInfo.id;
this.url += "&inviterId=" + this.inviterId;
this.url += "&isHost=" + this.isHost;
this.url += "&loginInfo=" + JSON.stringify(uni.getStorageSync("loginInfo"));
this.url += "&userInfos=" + JSON.stringify(this.userInfos);
this.url += "&config=" + JSON.stringify(this.configStore.webrtc);
},
}, },
onBackPress() { onWebviewMessage(event) {
console.log("onBackPress") console.log("来自webview的消息:" + JSON.stringify(event))
this.sendMessageToWebView("NAV_BACK", {}) switch (event.key) {
case "WV_READY":
this.initWebView();
break;
case "WV_CLOSE":
uni.navigateBack();
break;
case "INSERT_MESSAGE":
this.onInsertMessage(event.data);
break;
}
}, },
onLoad(options) { sendMessageToWebView(key, message) {
uni.$on('WS_RTC_GROUP', msg => { // webview100ms
// web-view if (!this.wv) {
this.sendMessageToWebView("RTC_MESSAGE", msg); setTimeout(() => this.sendMessageToWebView(key, message), 100)
}) return;
// #ifdef H5
window.onmessage = (e) => {
this.onWebviewMessage(e.data.data.arg);
} }
let event = {
key: key,
data: message
}
// #ifdef APP-PLUS
this.wv.evalJS(`onEvent('${encodeURIComponent(JSON.stringify(event))}')`)
// #endif
// #ifdef H5
this.wv.postMessage(event, '*');
// #endif // #endif
//
this.isHost = JSON.parse(options.isHost);
//
this.inviterId = options.inviterId;
//
this.groupId = options.groupId;
//
this.userInfos = JSON.parse(decodeURIComponent(options.userInfos));
// url
this.initUrl();
}, },
onUnload() { initWebView() {
uni.$off('WS_RTC_GROUP') // #ifdef APP-PLUS
// APPwebview
this.wv = this.$scope.$getAppWebview().children()[0]
// #endif
// #ifdef H5
// H5webviewiframe
this.wv = document.getElementById('chat-video-wv').contentWindow
// #endif
},
initUrl() {
this.url = "/hybrid/html/rtc-group/index.html?";
this.url += "baseUrl=" + UNI_APP.BASE_URL;
this.url += "&groupId=" + this.groupId;
this.url += "&userId=" + this.userStore.userInfo.id;
this.url += "&inviterId=" + this.inviterId;
this.url += "&isHost=" + this.isHost;
this.url += "&loginInfo=" + JSON.stringify(uni.getStorageSync("loginInfo"));
this.url += "&userInfos=" + JSON.stringify(this.userInfos);
this.url += "&config=" + JSON.stringify(this.configStore.webrtc);
},
},
onBackPress() {
console.log("onBackPress")
this.sendMessageToWebView("NAV_BACK", {})
},
onLoad(options) {
uni.$on('WS_RTC_GROUP', msg => {
// web-view
this.sendMessageToWebView("RTC_MESSAGE", msg);
})
// #ifdef H5
window.onmessage = (e) => {
this.onWebviewMessage(e.data.data.arg);
} }
// #endif
//
this.isHost = JSON.parse(options.isHost);
//
this.inviterId = options.inviterId;
//
this.groupId = options.groupId;
//
this.userInfos = JSON.parse(decodeURIComponent(options.userInfos));
// url
this.initUrl();
},
onUnload() {
uni.$off('WS_RTC_GROUP')
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat-group-video { .chat-group-video {
.header { .header {
display: flex; display: flex;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
height: 60rpx; height: 60rpx;
padding: 5px; padding: 5px;
background-color: white; background-color: white;
line-height: 50px; line-height: 50px;
font-size: 40rpx; font-size: 40rpx;
font-weight: 600; font-weight: 600;
.btn-side { .btn-side {
position: absolute; position: absolute;
line-height: 60rpx; line-height: 60rpx;
font-size: 28rpx; font-size: 28rpx;
cursor: pointer; cursor: pointer;
&.left { &.left {
left: 30rpx; left: 30rpx;
} }
&.right { &.right {
right: 30rpx; right: 30rpx;
}
} }
} }
} }
}
</style> </style>

169
im-uniapp/pages/chat/chat-private-video.vue

@ -3,101 +3,98 @@
</template> </template>
<script> <script>
import UNI_APP from '@/.env.js' import UNI_APP from '@/.env.js'
export default { export default {
data() { data() {
return { return {
url: "", url: "",
wv: '', wv: '',
mode: "video", mode: "video",
isHost: true, isHost: true,
friend: {} friend: {}
} }
},
methods: {
onMessage(e) {
this.onWebviewMessage(e.detail.data[0]);
}, },
methods: { onWebviewMessage(event) {
onMessage(e) { console.log("来自webview的消息:" + JSON.stringify(event))
this.onWebviewMessage(e.detail.data[0]); switch (event.key) {
}, case "WV_READY":
onWebviewMessage(event) { this.initWebView();
console.log("来自webview的消息:" + JSON.stringify(event)) break;
switch (event.key) { case "WV_CLOSE":
case "WV_READY": uni.navigateBack();
this.initWebView(); break;
break; }
case "WV_CLOSE":
uni.navigateBack();
break;
}
},
sendMessageToWebView(key, message) {
// webview100ms
if (!this.wv) {
setTimeout(() => this.sendMessageToWebView(key, message), 100)
return;
}
let event = {
key: key,
data: message
}
// #ifdef APP-PLUS
this.wv.evalJS(`onEvent('${encodeURIComponent(JSON.stringify(event))}')`)
// #endif
// #ifdef H5
this.wv.postMessage(event, '*');
// #endif
},
initWebView() {
// #ifdef APP-PLUS
// APPwebview
this.wv = this.$scope.$getAppWebview().children()[0]
// #endif
// #ifdef H5
// H5webviewiframe
this.wv = document.getElementById('chat-video-wv').contentWindow
// #endif
},
initUrl(){
this.url = "/hybrid/html/rtc-private/index.html";
this.url += "?mode="+this.mode;
this.url += "&isHost="+this.isHost;
this.url += "&baseUrl="+UNI_APP.BASE_URL;
this.url += "&loginInfo="+JSON.stringify(uni.getStorageSync("loginInfo"));
this.url += "&userInfo="+JSON.stringify(this.userStore.userInfo);
this.url += "&friend="+JSON.stringify(this.friend);
this.url += "&config=" + JSON.stringify(this.configStore.webrtc);
},
}, },
onBackPress() { sendMessageToWebView(key, message) {
this.sendMessageToWebView("NAV_BACK",{}) // webview100ms
if (!this.wv) {
setTimeout(() => this.sendMessageToWebView(key, message), 100)
return;
}
let event = {
key: key,
data: message
}
// #ifdef APP-PLUS
this.wv.evalJS(`onEvent('${encodeURIComponent(JSON.stringify(event))}')`)
// #endif
// #ifdef H5
this.wv.postMessage(event, '*');
// #endif
}, },
onLoad(options) { initWebView() {
uni.$on('WS_RTC_PRIVATE', msg => { // #ifdef APP-PLUS
// web-view // APPwebview
this.sendMessageToWebView("RTC_MESSAGE", msg); this.wv = this.$scope.$getAppWebview().children()[0]
}) // #endif
// #ifdef H5 // #ifdef H5
window.onmessage = (e) => { // H5webviewiframe
this.onWebviewMessage(e.data.data.arg); this.wv = document.getElementById('chat-video-wv').contentWindow
}
// #endif // #endif
// /
this.mode = options.mode;
//
this.isHost = JSON.parse(options.isHost);
//
this.friend = JSON.parse(decodeURIComponent(options.friend));
// url
this.initUrl();
}, },
onUnload() { initUrl() {
uni.$off('WS_RTC_PRIVATE') this.url = "/hybrid/html/rtc-private/index.html";
this.url += "?mode=" + this.mode;
this.url += "&isHost=" + this.isHost;
this.url += "&baseUrl=" + UNI_APP.BASE_URL;
this.url += "&loginInfo=" + JSON.stringify(uni.getStorageSync("loginInfo"));
this.url += "&userInfo=" + JSON.stringify(this.userStore.userInfo);
this.url += "&friend=" + JSON.stringify(this.friend);
this.url += "&config=" + JSON.stringify(this.configStore.webrtc);
},
},
onBackPress() {
this.sendMessageToWebView("NAV_BACK", {})
},
onLoad(options) {
uni.$on('WS_RTC_PRIVATE', msg => {
// web-view
this.sendMessageToWebView("RTC_MESSAGE", msg);
})
// #ifdef H5
window.onmessage = (e) => {
this.onWebviewMessage(e.data.data.arg);
} }
// #endif
// /
this.mode = options.mode;
//
this.isHost = JSON.parse(options.isHost);
//
this.friend = JSON.parse(decodeURIComponent(options.friend));
// url
this.initUrl();
},
onUnload() {
uni.$off('WS_RTC_PRIVATE')
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.chat-web-view {}
.chat-web-view{
}
</style> </style>

227
im-uniapp/pages/chat/chat.vue

@ -1,6 +1,6 @@
<template> <template>
<view class="tab-page"> <view class="tab-page">
<nav-bar search @search="showSearch = !showSearch">消息</nav-bar> <nav-bar search @search="showSearch = !showSearch">消息</nav-bar>
<view v-if="loading" class="chat-loading"> <view v-if="loading" class="chat-loading">
<loading :size="50" :mask="false"> <loading :size="50" :mask="false">
<view>消息接收中...</view> <view>消息接收中...</view>
@ -11,15 +11,13 @@
<uni-search-bar radius="100" v-model="searchText" cancelButton="none" placeholder="搜索"></uni-search-bar> <uni-search-bar radius="100" v-model="searchText" cancelButton="none" placeholder="搜索"></uni-search-bar>
</view> </view>
</view> </view>
<view class="chat-tip" v-if="!loading && chatStore.chats.length==0"> <view class="chat-tip" v-if="!loading && chatStore.chats.length == 0">
温馨提示您现在还没有任何聊天消息快跟您的好友发起聊天吧~ 温馨提示您现在还没有任何聊天消息快跟您的好友发起聊天吧~
</view> </view>
<scroll-view class="scroll-bar" v-else scroll-with-animation="true" scroll-y="true"> <scroll-view class="scroll-bar" v-else scroll-with-animation="true" scroll-y="true">
<view v-for="(chat,index) in chatStore.chats" :key="index"> <view v-for="(chat, index) in chatStore.chats" :key="index">
<pop-menu v-if="isShowChat(chat)" :items="menu.items" <pop-menu v-if="isShowChat(chat)" :items="menu.items" @select="onSelectMenu($event, index)">
@select="onSelectMenu($event,index)"> <chat-item :chat="chat" :index="index" :active="menu.chatIdx == index"></chat-item>
<chat-item :chat="chat" :index="index"
:active="menu.chatIdx==index"></chat-item>
</pop-menu> </pop-menu>
</view> </view>
</scroll-view> </scroll-view>
@ -27,131 +25,130 @@
</template> </template>
<script> <script>
import useChatStore from '@/store/chatStore.js'
export default {
export default { data() {
data() { return {
return { showSearch: false,
showSearch: false, searchText: "",
searchText: "", menu: {
menu: { show: false,
show: false, style: "",
style: "", chatIdx: -1,
chatIdx: -1, isTouchMove: false,
isTouchMove: false, items: [{
items: [{ key: 'DELETE',
key: 'DELETE', name: '删除该聊天',
name: '删除该聊天', icon: 'trash',
icon: 'trash', color: '#e64e4e'
color: '#e64e4e' },
}, {
{ key: 'TOP',
key: 'TOP', name: '置顶该聊天',
name: '置顶该聊天', icon: 'arrow-up'
icon: 'arrow-up'
}
]
} }
]
} }
}, }
methods: { },
onSelectMenu(item,chatIdx) { methods: {
switch (item.key) { onSelectMenu(item, chatIdx) {
case 'DELETE': switch (item.key) {
this.removeChat(chatIdx); case 'DELETE':
break; this.removeChat(chatIdx);
case 'TOP': break;
this.moveToTop(chatIdx); case 'TOP':
break; this.moveToTop(chatIdx);
default: break;
break; default:
} break;
this.menu.show = false;
},
removeChat(chatIdx) {
this.chatStore.removeChat(chatIdx);
},
moveToTop(chatIdx) {
this.chatStore.moveTop(chatIdx);
},
isShowChat(chat){
if(chat.delete){
return false;
}
return !this.searchText || chat.showName.includes(this.searchText)
},
refreshUnreadBadge() {
if (this.unreadCount > 0) {
uni.setTabBarBadge({
index: 0,
text: this.unreadCount + ""
})
} else {
uni.removeTabBarBadge({
index: 0,
complete: () => {}
})
}
} }
this.menu.show = false;
}, },
computed: { removeChat(chatIdx) {
unreadCount() { this.chatStore.removeChat(chatIdx);
let count = 0; },
this.chatStore.chats.forEach(chat => { moveToTop(chatIdx) {
if (!chat.delete) { this.chatStore.moveTop(chatIdx);
count += chat.unreadCount; },
} isShowChat(chat) {
}) if (chat.delete) {
return count; return false;
},
loading() {
return this.chatStore.loadingGroupMsg || this.chatStore.loadingPrivateMsg
} }
return !this.searchText || chat.showName.includes(this.searchText)
}, },
watch: { refreshUnreadBadge() {
unreadCount(newCount, oldCount) { if (this.unreadCount > 0) {
this.refreshUnreadBadge(); uni.setTabBarBadge({
index: 0,
text: this.unreadCount + ""
})
} else {
uni.removeTabBarBadge({
index: 0,
complete: () => { }
})
} }
}
},
computed: {
unreadCount() {
let count = 0;
this.chatStore.chats.forEach(chat => {
if (!chat.delete) {
count += chat.unreadCount;
}
})
return count;
}, },
onShow() { loading() {
return this.chatStore.loadingGroupMsg || this.chatStore.loadingPrivateMsg
}
},
watch: {
unreadCount(newCount, oldCount) {
this.refreshUnreadBadge(); this.refreshUnreadBadge();
} }
},
onShow() {
this.refreshUnreadBadge();
} }
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.tab-page { .tab-page {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.chat-tip { .chat-tip {
position: absolute; position: absolute;
top: 400rpx; top: 400rpx;
padding: 50rpx; padding: 50rpx;
line-height: 50rpx; line-height: 50rpx;
text-align: center; text-align: center;
color: $im-text-color-lighter; color: $im-text-color-lighter;
} }
.chat-loading { .chat-loading {
display: block; display: block;
width: 100%; width: 100%;
height: 120rpx; height: 120rpx;
background: white; background: white;
position: fixed; position: fixed;
top: 0; top: 0;
z-index: 999; z-index: 999;
color: $im-text-color-lighter; color: $im-text-color-lighter;
.loading-box { .loading-box {
position: relative; position: relative;
}
} }
}
.scroll-bar { .scroll-bar {
flex: 1; flex: 1;
height: 100%; height: 100%;
}
} }
}
</style> </style>

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

@ -9,17 +9,17 @@
<view class="info-item"> <view class="info-item">
<view class="info-primary"> <view class="info-primary">
<text class="info-username"> <text class="info-username">
{{userInfo.userName}} {{ userInfo.userName }}
</text> </text>
<text v-show="userInfo.sex==0" class="iconfont icon-man" color="darkblue"></text> <text v-show="userInfo.sex == 0" class="iconfont icon-man" color="darkblue"></text>
<text v-show="userInfo.sex==1" class="iconfont icon-girl" color="darkred"></text> <text v-show="userInfo.sex == 1" class="iconfont icon-girl" color="darkred"></text>
</view> </view>
<view class="info-text"> <view class="info-text">
<text class="label-text"> <text class="label-text">
昵称: 昵称:
</text> </text>
<text class="content-text"> <text class="content-text">
{{userInfo.nickName}} {{ userInfo.nickName }}
</text> </text>
</view> </view>
<view class="info-text"> <view class="info-text">
@ -28,7 +28,7 @@
签名: 签名:
</text> </text>
<text class="content-text"> <text class="content-text">
{{userInfo.signature}} {{ userInfo.signature }}
</text> </text>
</view> </view>
</view> </view>
@ -45,174 +45,174 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
userInfo: {} userInfo: {}
}
},
methods: {
onShowFullImage() {
let imageUrl = this.userInfo.headImage;
if (imageUrl) {
uni.previewImage({
urls: [imageUrl]
})
} }
}, },
methods: { onSendMessage() {
onShowFullImage() { let chat = {
let imageUrl = this.userInfo.headImage; type: 'PRIVATE',
if (imageUrl) { targetId: this.userInfo.id,
uni.previewImage({ showName: this.userInfo.nickName,
urls: [imageUrl] headImage: this.userInfo.headImage,
}) };
this.chatStore.openChat(chat);
let chatIdx = this.chatStore.findChatIdx(chat);
uni.navigateTo({
url: "/pages/chat/chat-box?chatIdx=" + chatIdx
})
},
onAddFriend() {
this.$http({
url: "/friend/add?friendId=" + this.userInfo.id,
method: "POST"
}).then((data) => {
let friend = {
id: this.userInfo.id,
nickName: this.userInfo.nickName,
headImage: this.userInfo.headImageThumb,
online: this.userInfo.online
} }
}, this.friendStore.addFriend(friend);
onSendMessage() { uni.showToast({
let chat = { title: '对方已成为您的好友',
type: 'PRIVATE', icon: 'none'
targetId: this.userInfo.id,
showName: this.userInfo.nickName,
headImage: this.userInfo.headImage,
};
this.chatStore.openChat(chat);
let chatIdx = this.chatStore.findChatIdx(chat);
uni.navigateTo({
url: "/pages/chat/chat-box?chatIdx=" + chatIdx
}) })
}, })
onAddFriend() { },
this.$http({ onDelFriend() {
url: "/friend/add?friendId=" + this.userInfo.id, uni.showModal({
method: "POST" title: "确认删除",
}).then((data) => { content: `确认删除 '${this.userInfo.nickName}',并删除聊天记录吗?`,
let friend = { success: (res) => {
id: this.userInfo.id, if (res.cancel)
nickName: this.userInfo.nickName, return;
headImage: this.userInfo.headImageThumb, this.$http({
online: this.userInfo.online url: `/friend/delete/${this.userInfo.id}`,
} method: 'delete'
this.friendStore.addFriend(friend); }).then((data) => {
uni.showToast({ this.friendStore.removeFriend(this.userInfo.id);
title: '对方已成为您的好友', this.chatStore.removePrivateChat(this.userInfo.id);
icon: 'none' uni.showToast({
}) title: `与 '${this.userInfo.nickName}'的好友关系已解除`,
}) icon: 'none'
},
onDelFriend() {
uni.showModal({
title: "确认删除",
content: `确认删除 '${this.userInfo.nickName}',并删除聊天记录吗?`,
success: (res) => {
if (res.cancel)
return;
this.$http({
url: `/friend/delete/${this.userInfo.id}`,
method: 'delete'
}).then((data) => {
this.friendStore.removeFriend(this.userInfo.id);
this.chatStore.removePrivateChat(this.userInfo.id);
uni.showToast({
title: `与 '${this.userInfo.nickName}'的好友关系已解除`,
icon: 'none'
})
}) })
} })
}) }
}, })
updateFriendInfo() {
// storestore
let friend = JSON.parse(JSON.stringify(this.friendInfo));
friend.headImage = this.userInfo.headImageThumb;
friend.nickName = this.userInfo.nickName;
this.$http({
url: "/friend/update",
method: "PUT",
data: friend
}).then(() => {
//
this.friendStore.updateFriend(friend);
//
this.chatStore.updateChatFromFriend(this.userInfo);
})
},
loadUserInfo(id) {
this.$http({
url: "/user/find/" + id,
method: 'GET'
}).then((user) => {
this.userInfo = user;
//
if (this.isFriend && (this.userInfo.headImageThumb != this.friendInfo.headImage ||
this.userInfo.nickName != this.friendInfo.nickName)) {
this.updateFriendInfo()
}
})
}
}, },
computed: { updateFriendInfo() {
isFriend() { // storestore
return !!this.friendInfo; let friend = JSON.parse(JSON.stringify(this.friendInfo));
}, friend.headImage = this.userInfo.headImageThumb;
friendInfo() { friend.nickName = this.userInfo.nickName;
let friends = this.friendStore.friends; this.$http({
let friend = friends.find((f) => f.id == this.userInfo.id); url: "/friend/update",
return friend; method: "PUT",
} data: friend
}).then(() => {
//
this.friendStore.updateFriend(friend);
//
this.chatStore.updateChatFromFriend(this.userInfo);
})
}, },
onLoad(options) { loadUserInfo(id) {
// this.$http({
this.loadUserInfo(options.id); url: "/user/find/" + id,
method: 'GET'
}).then((user) => {
this.userInfo = user;
//
if (this.isFriend && (this.userInfo.headImageThumb != this.friendInfo.headImage ||
this.userInfo.nickName != this.friendInfo.nickName)) {
this.updateFriendInfo()
}
})
} }
},
computed: {
isFriend() {
return !!this.friendInfo;
},
friendInfo() {
let friends = this.friendStore.friends;
let friend = friends.find((f) => f.id == this.userInfo.id);
return friend;
}
},
onLoad(options) {
//
this.loadUserInfo(options.id);
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.user-info { .user-info {
.content { .content {
height: 200rpx; height: 200rpx;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
padding: 20rpx; padding: 20rpx;
.info-item { .info-item {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
flex-direction: column; flex-direction: column;
padding-left: 40rpx; padding-left: 40rpx;
flex: 1; flex: 1;
.info-text { .info-text {
line-height: 1.5; line-height: 1.5;
//margin-bottom: 10rpx; //margin-bottom: 10rpx;
} }
.label-text { .label-text {
font-size: $im-font-size-small; font-size: $im-font-size-small;
color: $im-text-color-light; color: $im-text-color-light;
} }
.content-text { .content-text {
font-size: $im-font-size-small; font-size: $im-font-size-small;
color: $im-text-color-light; color: $im-text-color-light;
} }
.info-primary { .info-primary {
display: flex; display: flex;
align-items: center; align-items: center;
margin-bottom: 10rpx; margin-bottom: 10rpx;
.info-username { .info-username {
font-size: $im-font-size-large; font-size: $im-font-size-large;
font-weight: 600; font-weight: 600;
} }
.icon-man { .icon-man {
color: $im-text-color; color: $im-text-color;
font-size: $im-font-size-large; font-size: $im-font-size-large;
padding-left: 10rpx; padding-left: 10rpx;
} }
.icon-girl { .icon-girl {
color: darkred; color: darkred;
}
} }
} }
} }
} }
}
</style> </style>

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

@ -1,19 +1,19 @@
<template> <template>
<view class="page friend-add"> <view class="page friend-add">
<nav-bar back>添加好友</nav-bar> <nav-bar back>添加好友</nav-bar>
<view class="nav-bar"> <view class="nav-bar">
<view class="nav-search"> <view class="nav-search">
<uni-search-bar v-model="searchText" radius="100" :focus="true" @confirm="onSearch()" @cancel="onCancel()" <uni-search-bar v-model="searchText" radius="100" :focus="true" @confirm="onSearch()"
placeholder="用户名/昵称"></uni-search-bar> @cancel="onCancel()" placeholder="用户名/昵称"></uni-search-bar>
</view> </view>
</view> </view>
<view class="user-items"> <view class="user-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true"> <scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="(user) in users" :key="user.id" v-show="user.id != userStore.userInfo.id"> <view v-for="(user) in users" :key="user.id" v-show="user.id != userStore.userInfo.id">
<view class="user-item"> <view class="user-item">
<head-image :id="user.id" :name="user.nickName" <head-image :id="user.id" :name="user.nickName" :online="user.online"
:online="user.online" :url="user.headImage"></head-image> :url="user.headImage"></head-image>
<view class="user-name">{{ user.nickName}}</view> <view class="user-name">{{ user.nickName }}</view>
<view class="user-btns"> <view class="user-btns">
<button type="primary" v-show="!isFriend(user.id)" size="mini" <button type="primary" v-show="!isFriend(user.id)" size="mini"
@click.stop="onAddFriend(user)">加为好友</button> @click.stop="onAddFriend(user)">加为好友</button>
@ -27,91 +27,92 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
searchText: "", searchText: "",
users: [] users: []
} }
},
methods: {
onCancel() {
uni.navigateBack();
}, },
methods: { onSearch() {
onCancel() { this.$http({
uni.navigateBack(); url: "/user/findByName?name=" + this.searchText,
}, method: "GET"
onSearch() { }).then((data) => {
this.$http({ this.users = data;
url: "/user/findByName?name=" + this.searchText, })
method: "GET" },
}).then((data) => { onAddFriend(user) {
this.users = data; this.$http({
}) url: "/friend/add?friendId=" + user.id,
}, method: "POST"
onAddFriend(user) { }).then((data) => {
this.$http({ let friend = {
url: "/friend/add?friendId=" + user.id, id: user.id,
method: "POST" nickName: user.nickName,
}).then((data) => { headImage: user.headImage,
let friend = { online: user.online
id: user.id, }
nickName: user.nickName, this.friendStore.addFriend(friend);
headImage: user.headImage, uni.showToast({
online: user.online title: "添加成功,对方已成为您的好友",
} icon: "none"
this.friendStore.addFriend(friend);
uni.showToast({
title: "添加成功,对方已成为您的好友",
icon: "none"
})
})
},
onShowUserInfo(user) {
uni.navigateTo({
url: "/pages/common/user-info?id=" + user.id
}) })
}, })
isFriend(userId) { },
let friends = this.friendStore.friends; onShowUserInfo(user) {
let friend = friends.find((f) => f.id == userId); uni.navigateTo({
return !!friend; url: "/pages/common/user-info?id=" + user.id
} })
},
isFriend(userId) {
let friends = this.friendStore.friends;
let friend = friends.find((f) => f.id == userId);
return !!friend;
} }
} }
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.friend-add { .friend-add {
position: relative;
display: flex;
flex-direction: column;
.user-items {
position: relative; position: relative;
display: flex; flex: 1;
flex-direction: column; overflow: hidden;
.user-items{ .user-item {
height: 120rpx;
display: flex;
margin-bottom: 1rpx;
position: relative; position: relative;
flex: 1; padding: 0 30rpx;
overflow: hidden; align-items: center;
.user-item { background-color: white;
height: 120rpx; white-space: nowrap;
display: flex;
margin-bottom: 1rpx; .user-name {
position: relative; flex: 1;
padding: 0 30rpx ; padding-left: 20rpx;
align-items: center; font-size: $im-font-size;
background-color: white; line-height: 60rpx;
white-space: nowrap; white-space: nowrap;
overflow: hidden;
.user-name {
flex:1;
padding-left: 20rpx;
font-size: $im-font-size;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
}
}
.scroll-bar {
height: 100%;
} }
} }
.scroll-bar {
height: 100%;
}
} }
}
</style> </style>

228
im-uniapp/pages/friend/friend.vue

@ -1,146 +1,150 @@
<template> <template>
<nav-bar add search @add="onAddNewFriends" @search="showSearch = !showSearch">好友</nav-bar>
<view class="tab-page friend"> <view class="tab-page friend">
<nav-bar add search @add="onAddNewFriends" @search="showSearch = !showSearch">好友</nav-bar>
<view class="nav-bar" v-if="showSearch"> <view class="nav-bar" v-if="showSearch">
<view class="nav-search"> <view class="nav-search">
<uni-search-bar v-model="searchText" radius="100" cancelButton="none" placeholder="点击搜索好友"></uni-search-bar> <uni-search-bar v-model="searchText" radius="100" cancelButton="none"
placeholder="点击搜索好友"></uni-search-bar>
</view> </view>
</view> </view>
<view class="friend-tip" v-if="friends.length==0"> <view class="friend-tip" v-if="friends.length == 0">
温馨提示您现在还没有任何好友快点击右上方'+'按钮添加好友吧~ 温馨提示您现在还没有任何好友快点击右上方'+'按钮添加好友吧~
</view> </view>
<view class="friend-items" v-else> <view class="friend-items" v-else>
<up-index-list :index-list="friendIdx" > <up-index-list :index-list="friendIdx">
<template v-for="(friends,i) in friendGroups"> <template v-for="(friends, i) in friendGroups">
<up-index-item> <up-index-item>
<up-index-anchor :text="friendIdx[i]=='*'?'在线':friendIdx[i]"></up-index-anchor> <up-index-anchor :text="friendIdx[i] == '*' ? '在线' : friendIdx[i]"></up-index-anchor>
<view v-for="(friend,idx) in friends" :key="idx"> <view v-for="(friend, idx) in friends" :key="idx">
<friend-item :friend="friend"></friend-item> <friend-item :friend="friend"></friend-item>
</view> </view>
</up-index-item> </up-index-item>
</template> </template>
</up-index-list> </up-index-list>
</view> </view>
</view> </view>
</template> </template>
<script> <script>
import { pinyin } from 'pinyin-pro'; import { pinyin } from 'pinyin-pro';
export default { export default {
data() { data() {
return { return {
showSearch: false, showSearch: false,
searchText: '' searchText: ''
} }
},
methods: {
onAddNewFriends() {
uni.navigateTo({
url: "/pages/friend/friend-add"
})
},
firstLetter(strText) {
// 使pinyin-pro
let pinyinOptions = {
toneType: 'none', //
type: 'normal' //
};
let pyText = pinyin(strText, pinyinOptions);
return pyText[0];
},
isEnglish(character) {
return /^[A-Za-z]+$/.test(character);
}
},
computed: {
friends() {
return this.friendStore.friends;
},
friendGroupMap() {
//
let groupMap = new Map();
this.friends.forEach((f) => {
if (this.searchText && !f.nickName.includes(this.searchText)) {
return;
}
let letter = this.firstLetter(f.nickName).toUpperCase();
// #
if (!this.isEnglish(letter)) {
letter = "#"
}
if (f.online) {
letter = '*'
}
if (groupMap.has(letter)) {
groupMap.get(letter).push(f);
} else {
groupMap.set(letter, [f]);
}
})
//
let arrayObj = Array.from(groupMap);
arrayObj.sort((a, b) => {
// #
if (a[0] == '#' || b[0] == '#') {
return b[0].localeCompare(a[0])
}
return a[0].localeCompare(b[0])
})
groupMap = new Map(arrayObj.map(i => [i[0], i[1]]));
return groupMap;
}, },
methods: { friendIdx() {
onAddNewFriends() { return Array.from(this.friendGroupMap.keys());
uni.navigateTo({
url: "/pages/friend/friend-add"
})
},
firstLetter(strText) {
// 使pinyin-pro
let pinyinOptions = {
toneType: 'none', //
type: 'normal' //
};
let pyText = pinyin(strText, pinyinOptions);
return pyText[0];
},
isEnglish(character) {
return /^[A-Za-z]+$/.test(character);
}
}, },
computed: { friendGroups() {
friends() { return Array.from(this.friendGroupMap.values());
return this.friendStore.friends;
},
friendGroupMap(){
//
let groupMap = new Map();
this.friends.forEach((f) => {
if(this.searchText && !f.nickName.includes(this.searchText)){
return;
}
let letter = this.firstLetter(f.nickName).toUpperCase();
// #
if (!this.isEnglish(letter)) {
letter = "#"
}
if (f.online) {
letter = '*'
}
if (groupMap.has(letter)) {
groupMap.get(letter).push(f);
} else {
groupMap.set(letter, [f]);
}
})
//
let arrayObj = Array.from(groupMap);
arrayObj.sort((a, b) => {
// #
if (a[0] == '#' || b[0] == '#') {
return b[0].localeCompare(a[0])
}
return a[0].localeCompare(b[0])
})
groupMap = new Map(arrayObj.map(i => [i[0], i[1]]));
return groupMap;
},
friendIdx(){
return Array.from(this.friendGroupMap.keys());
},
friendGroups(){
return Array.from(this.friendGroupMap.values());
}
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.friend { .friend {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
:deep(.u-index-anchor){ :deep(.u-index-anchor) {
height: 60rpx !important; height: 60rpx !important;
background-color: unset !important; background-color: unset !important;
border-bottom: none !important; border-bottom: none !important;
} }
:deep(.u-index-anchor__text){
color: $im-text-color !important;
}
:deep(.u-index-list__letter__item){
width: 48rpx !important;
height: 48rpx !important;
}
:deep(.u-index-list__letter__item__index){
font-size: $im-font-size-small !important;
}
.friend-tip { :deep(.u-index-anchor__text) {
position: absolute; color: $im-text-color !important;
top: 400rpx; }
padding: 50rpx;
text-align: center; :deep(.u-index-list__letter__item) {
line-height: 50rpx; width: 48rpx !important;
color: $im-text-color-lighter; height: 48rpx !important;
} }
:deep(.u-index-list__letter__item__index) {
font-size: $im-font-size-small !important;
}
.friend-tip {
position: absolute;
top: 400rpx;
padding: 50rpx;
text-align: center;
line-height: 50rpx;
color: $im-text-color-lighter;
}
.friend-items {
flex: 1;
padding: 0;
overflow: hidden;
position: relative;
.friend-items { .scroll-bar {
flex: 1; height: 100%;
padding: 0;
overflow: hidden;
position: relative;
.scroll-bar {
height: 100%;
}
} }
} }
}
</style> </style>

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

@ -1,156 +1,157 @@
<template> <template>
<nav-bar back>修改群资料</nav-bar> <view v-if="userStore.userInfo.type == 1" class="page group-edit">
<view v-if="userStore.userInfo.type == 1" class="page group-edit"> <nav-bar back>修改群资料</nav-bar>
<uni-card :is-shadow="false" is-full :border="false"> <uni-card :is-shadow="false" is-full :border="false">
<uni-forms ref="form" :modelValue="group" :rules="rules" validate-trigger="bind" label-position="top" <uni-forms ref="form" :modelValue="group" :rules="rules" validate-trigger="bind" label-position="top"
label-width="100%"> label-width="100%">
<uni-forms-item name="headImage" class="avatar"> <uni-forms-item name="headImage" class="avatar">
<image-upload v-if="isOwner" :onSuccess="onUnloadImageSuccess"> <image-upload v-if="isOwner" :onSuccess="onUnloadImageSuccess">
<image :src="group.headImageThumb" class="group-image"></image> <image :src="group.headImageThumb" class="group-image"></image>
</image-upload> </image-upload>
<head-image v-if="!isOwner" :name="group.showGroupName" <head-image v-if="!isOwner" :name="group.showGroupName" :url="group.headImageThumb"
:url="group.headImageThumb" :size="200"></head-image> :size="200"></head-image>
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="群聊名称" name="name" :required="true"> <uni-forms-item label="群聊名称" name="name" :required="true">
<uni-easyinput type="text" v-model="group.name" :disabled="!isOwner" placeholder="请输入群聊名称" /> <uni-easyinput type="text" v-model="group.name" :disabled="!isOwner" placeholder="请输入群聊名称" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="群聊备注" name="remarkGroupName"> <uni-forms-item label="群聊备注" name="remarkGroupName">
<uni-easyinput v-model="group.remarkGroupName" type="text" :placeholder="group.name" /> <uni-easyinput v-model="group.remarkGroupName" type="text" :placeholder="group.name" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="我在本群的昵称" name="remarkNickName"> <uni-forms-item label="我在本群的昵称" name="remarkNickName">
<uni-easyinput v-model="group.remarkNickName" type="text" :placeholder="userStore.userInfo.nickName" /> <uni-easyinput v-model="group.remarkNickName" type="text"
</uni-forms-item> :placeholder="userStore.userInfo.nickName" />
<uni-forms-item label="群公告" name="notice"> </uni-forms-item>
<uni-easyinput type="textarea" v-model="group.notice" :disabled="!isOwner" placeholder="请输入群公告" /> <uni-forms-item label="群公告" name="notice">
</uni-forms-item> <uni-easyinput type="textarea" v-model="group.notice" :disabled="!isOwner" placeholder="请输入群公告" />
</uni-forms> </uni-forms-item>
</uni-card> </uni-forms>
<button class="bottom-btn" type="primary" @click="submit()">提交</button> </uni-card>
</view> <button class="bottom-btn" type="primary" @click="submit()">提交</button>
</view>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
group: {}, group: {},
rules: { rules: {
name: { name: {
rules: [{ rules: [{
required: true, required: true,
errorMessage: '请输入群聊名称', errorMessage: '请输入群聊名称',
}] }]
}
} }
}
}
},
methods: {
submit() {
if (this.group.id) {
this.modifyGroup();
} else {
this.createNewGroup();
} }
}, },
onUnloadImageSuccess(file, res) {
this.group.headImage = res.data.originUrl;
this.group.headImageThumb = res.data.thumbUrl;
},
modifyGroup() {
this.$http({
url: "/group/modify",
method: "PUT",
data: this.group
}).then((group) => {
this.groupStore.updateGroup(group);
uni.showToast({
title: "修改群聊信息成功",
icon: 'none'
});
setTimeout(() => {
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.$vm.loadGroupInfo();
uni.navigateBack();
}, 1000);
methods: { })
submit() { },
if (this.group.id) { createNewGroup() {
this.modifyGroup(); this.$http({
} else { url: "/group/create",
this.createNewGroup(); method: 'POST',
} data: this.group
}, }).then((group) => {
onUnloadImageSuccess(file, res) { this.groupStore.addGroup(group);
this.group.headImage = res.data.originUrl; uni.showToast({
this.group.headImageThumb = res.data.thumbUrl; title: `群聊创建成功,快邀请小伙伴进群吧`,
}, icon: 'none',
modifyGroup() { duration: 1500
this.$http({ });
url: "/group/modify", setTimeout(() => {
method: "PUT", uni.navigateTo({
data: this.group url: "/pages/group/group-info?id=" + group.id
}).then((group) => {
this.groupStore.updateGroup(group);
uni.showToast({
title: "修改群聊信息成功",
icon: 'none'
});
setTimeout(() => {
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.$vm.loadGroupInfo();
uni.navigateBack();
}, 1000);
})
},
createNewGroup() {
this.$http({
url: "/group/create",
method: 'POST',
data: this.group
}).then((group) => {
this.groupStore.addGroup(group);
uni.showToast({
title: `群聊创建成功,快邀请小伙伴进群吧`,
icon: 'none',
duration: 1500
}); });
setTimeout(() => { }, 1500)
uni.navigateTo({
url: "/pages/group/group-info?id=" + group.id
});
}, 1500)
})
},
loadGroupInfo(id) {
this.$http({
url: `/group/find/${id}`,
method: 'GET'
}).then((group) => {
this.group = group;
//
this.chatStore.updateChatFromGroup(group);
//
this.groupStore.updateGroup(group);
}); })
},
initNewGroup() {
let userInfo = this.userStore.userInfo;
this.group = {
name: `${userInfo.userName}创建的群聊`,
headImage: userInfo.headImage,
headImageThumb: userInfo.headImageThumb,
ownerId: this.userStore.userInfo.id
}
}
}, },
computed: { loadGroupInfo(id) {
isOwner() { this.$http({
return this.userStore.userInfo.id == this.group.ownerId url: `/group/find/${id}`,
} method: 'GET'
}).then((group) => {
this.group = group;
//
this.chatStore.updateChatFromGroup(group);
//
this.groupStore.updateGroup(group);
});
}, },
onLoad(options) { initNewGroup() {
if (options.id) { let userInfo = this.userStore.userInfo;
// this.group = {
this.loadGroupInfo(options.id); name: `${userInfo.userName}创建的群聊`,
} else { headImage: userInfo.headImage,
// headImageThumb: userInfo.headImageThumb,
this.initNewGroup(); ownerId: this.userStore.userInfo.id
} }
} }
},
computed: {
isOwner() {
return this.userStore.userInfo.id == this.group.ownerId
}
},
onLoad(options) {
if (options.id) {
//
this.loadGroupInfo(options.id);
} else {
//
this.initNewGroup();
}
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.group-edit { .group-edit {
//padding: 20rpx; //padding: 20rpx;
.group-image { .group-image {
width: 200rpx; width: 200rpx;
height: 200rpx; height: 200rpx;
border: 1px solid #ccc; border: 1px solid #ccc;
border-radius: 5%; border-radius: 5%;
}
} }
.avatar { }
margin-top: -30px;
} .avatar {
margin-top: -30px;
}
</style> </style>

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

@ -1,14 +1,14 @@
<template> <template>
<view v-if="userStore.userInfo.type == 1" class="page group-info"> <view v-if="userStore.userInfo.type == 1" class="page group-info">
<nav-bar back>群聊信息</nav-bar> <nav-bar back>群聊信息</nav-bar>
<view v-if="!group.quit" class="group-members"> <view v-if="!group.quit" class="group-members">
<view class="member-items"> <view class="member-items">
<view v-for="(member,idx) in groupMembers" :key="idx"> <view v-for="(member, idx) in groupMembers" :key="idx">
<view class="member-item" v-if="idx<9"> <view class="member-item" v-if="idx < 9">
<head-image :id="member.userId" :name="member.showNickName" :url="member.headImage" size="small" <head-image :id="member.userId" :name="member.showNickName" :url="member.headImage" size="small"
:online="member.online" ></head-image> :online="member.online"></head-image>
<view class="member-name"> <view class="member-name">
<text>{{member.showNickName}}</text> <text>{{ member.showNickName }}</text>
</view> </view>
</view> </view>
</view> </view>
@ -16,38 +16,37 @@
<uni-icons type="plusempty" size="20" color="#888888"></uni-icons> <uni-icons type="plusempty" size="20" color="#888888"></uni-icons>
</view> </view>
</view> </view>
<view class="member-more" @click="onShowMoreMmeber()">{{`查看全部群成员${groupMembers.length}`}}></view> <view class="member-more" @click="onShowMoreMmeber()">{{ `查看全部群成员${groupMembers.length}` }}></view>
</view> </view>
<view class="group-detail"> <view class="group-detail">
<uni-section title="群聊名称"> <uni-section title="群聊名称">
<template v-slot:right> <template v-slot:right>
<text class="detail-text">{{group.name}}</text> <text class="detail-text">{{ group.name }}</text>
</template> </template>
</uni-section> </uni-section>
<uni-section title="群主"> <uni-section title="群主">
<template v-slot:right> <template v-slot:right>
<text class="detail-text">{{ownerName}}</text> <text class="detail-text">{{ ownerName }}</text>
</template> </template>
</uni-section> </uni-section>
<uni-section title="群名备注"> <uni-section title="群名备注">
<template v-slot:right> <template v-slot:right>
<text class="detail-text"> {{group.remarkGroupName}}</text> <text class="detail-text"> {{ group.remarkGroupName }}</text>
</template> </template>
</uni-section> </uni-section>
<uni-section title="我在本群的昵称"> <uni-section title="我在本群的昵称">
<template v-slot:right> <template v-slot:right>
<text class="detail-text"> {{group.showNickName}}</text> <text class="detail-text"> {{ group.showNickName }}</text>
</template> </template>
</uni-section> </uni-section>
<uni-section v-if="group.notice" title="群公告"> <uni-section v-if="group.notice" title="群公告">
<uni-notice-bar :text="group.notice" /> <uni-notice-bar :text="group.notice" />
</uni-section> </uni-section>
<view v-if="!group.quit" class="group-edit" @click="onEditGroup()">修改群聊资料 > </view> <view v-if="!group.quit" class="group-edit" @click="onEditGroup()">修改群聊资料 > </view>
</view> </view>
<bar-group v-if="!group.quit"> <bar-group v-if="!group.quit">
<btn-bar type="primary" title="发送消息" @click="onSendMessage()"></btn-bar> <btn-bar type="primary" title="发送消息" @click="onSendMessage()"></btn-bar>
<btn-bar v-if="!isOwner" type="danger" title="退出群聊" @click="onQuitGroup()"></btn-bar> <btn-bar v-if="!isOwner" type="danger" title="退出群聊" @click="onQuitGroup()"></btn-bar>
<btn-bar v-if="isOwner" type="danger" title="解散群聊" @click="onDissolveGroup()"></btn-bar> <btn-bar v-if="isOwner" type="danger" title="解散群聊" @click="onDissolveGroup()"></btn-bar>
</bar-group> </bar-group>
@ -55,213 +54,213 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
groupId: null, groupId: null,
group:{}, group: {},
groupMembers: [] groupMembers: []
} }
},
methods: {
onFocusSearch() { },
onInviteMember() {
uni.navigateTo({
url: `/pages/group/group-invite?id=${this.groupId}`
})
},
onShowMoreMmeber() {
uni.navigateTo({
url: `/pages/group/group-member?id=${this.groupId}`
})
},
onEditGroup() {
uni.navigateTo({
url: `/pages/group/group-edit?id=${this.groupId}`
})
},
onSendMessage() {
let chat = {
type: 'GROUP',
targetId: this.group.id,
showName: this.group.showGroupName,
headImage: this.group.headImage,
};
this.chatStore.openChat(chat);
let chatIdx = this.chatStore.findChatIdx(chat);
uni.navigateTo({
url: "/pages/chat/chat-box?chatIdx=" + chatIdx
})
},
onQuitGroup() {
uni.showModal({
title: '确认退出?',
content: `退出群聊后将不再接受群里的消息,确认退出吗?`,
success: (res) => {
if (res.cancel)
return;
this.$http({
url: `/group/quit/${this.groupId}`,
method: 'DELETE'
}).then(() => {
uni.showModal({
title: `退出成功`,
content: `您已退出群聊'${this.group.name}'`,
showCancel: false,
success: () => {
setTimeout(() => {
uni.switchTab({
url: "/pages/group/group"
});
this.groupStore.removeGroup(this.groupId);
this.chatStore.removeGroupChat(this.groupId);
}, 100)
}
})
});
}
});
}, },
methods: { onDissolveGroup() {
onFocusSearch() {}, uni.showModal({
onInviteMember() { title: '确认解散?',
uni.navigateTo({ content: `确认要解散群聊'${this.group.name}'吗?`,
url: `/pages/group/group-invite?id=${this.groupId}` success: (res) => {
}) if (res.cancel)
}, return;
onShowMoreMmeber() { this.$http({
uni.navigateTo({ url: `/group/delete/${this.groupId}`,
url: `/pages/group/group-member?id=${this.groupId}` method: 'delete'
}) }).then(() => {
}, uni.showModal({
onEditGroup() { title: `解散成功`,
uni.navigateTo({ content: `群聊'${this.group.name}'已解散`,
url: `/pages/group/group-edit?id=${this.groupId}` showCancel: false,
}) success: () => {
}, setTimeout(() => {
onSendMessage() { uni.switchTab({
let chat = { url: "/pages/group/group"
type: 'GROUP', });
targetId: this.group.id, this.groupStore.removeGroup(this.groupId);
showName: this.group.showGroupName, this.chatStore.removeGroupChat(this.groupId);
headImage: this.group.headImage, }, 100)
}; }
this.chatStore.openChat(chat); })
let chatIdx = this.chatStore.findChatIdx(chat); });
uni.navigateTo({ }
url: "/pages/chat/chat-box?chatIdx=" + chatIdx });
})
},
onQuitGroup() {
uni.showModal({
title: '确认退出?',
content: `退出群聊后将不再接受群里的消息,确认退出吗?`,
success: (res) => {
if (res.cancel)
return;
this.$http({
url: `/group/quit/${this.groupId}`,
method: 'DELETE'
}).then(() => {
uni.showModal({
title: `退出成功`,
content: `您已退出群聊'${this.group.name}'`,
showCancel: false,
success: () => {
setTimeout(()=>{
uni.switchTab({
url:"/pages/group/group"
});
this.groupStore.removeGroup(this.groupId);
this.chatStore.removeGroupChat(this.groupId);
},100)
}
})
});
}
});
},
onDissolveGroup() {
uni.showModal({
title: '确认解散?',
content: `确认要解散群聊'${this.group.name}'吗?`,
success: (res) => {
if (res.cancel)
return;
this.$http({
url: `/group/delete/${this.groupId}`,
method: 'delete'
}).then(() => {
uni.showModal({
title: `解散成功`,
content: `群聊'${this.group.name}'已解散`,
showCancel: false,
success: () => {
setTimeout(()=>{
uni.switchTab({
url:"/pages/group/group"
});
this.groupStore.removeGroup(this.groupId);
this.chatStore.removeGroupChat(this.groupId);
},100)
}
})
});
}
});
}, },
loadGroupInfo() { loadGroupInfo() {
this.$http({ this.$http({
url: `/group/find/${this.groupId}`, url: `/group/find/${this.groupId}`,
method: 'GET' method: 'GET'
}).then((group) => { }).then((group) => {
this.group = group; this.group = group;
// //
this.chatStore.updateChatFromGroup(group); this.chatStore.updateChatFromGroup(group);
// //
this.groupStore.updateGroup(group); this.groupStore.updateGroup(group);
}); });
},
loadGroupMembers() {
console.log("loadGroupMembers")
this.$http({
url: `/group/members/${this.groupId}`,
method: "GET"
}).then((members) => {
this.groupMembers = members.filter(m => !m.quit);
})
}
}, },
computed: { loadGroupMembers() {
ownerName() { console.log("loadGroupMembers")
let member = this.groupMembers.find((m) => m.userId == this.group.ownerId); this.$http({
return member && member.showNickName; url: `/group/members/${this.groupId}`,
}, method: "GET"
isOwner() { }).then((members) => {
return this.group.ownerId == this.userStore.userInfo.id; this.groupMembers = members.filter(m => !m.quit);
} })
}
},
computed: {
ownerName() {
let member = this.groupMembers.find((m) => m.userId == this.group.ownerId);
return member && member.showNickName;
}, },
onLoad(options) { isOwner() {
this.groupId = options.id; return this.group.ownerId == this.userStore.userInfo.id;
//
this.loadGroupInfo(options.id);
//
this.loadGroupMembers(options.id)
} }
},
onLoad(options) {
this.groupId = options.id;
//
this.loadGroupInfo(options.id);
//
this.loadGroupMembers(options.id)
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.group-info { .group-info {
.group-members { .group-members {
padding: 30rpx; padding: 30rpx;
background: white; background: white;
.member-items { .member-items {
display: flex;
flex-wrap: wrap;
.member-item {
width: 120rpx;
display: flex; display: flex;
flex-wrap: wrap; flex-direction: column;
margin: 8rpx 2rpx;
position: relative;
align-items: center;
padding-right: 5px;
white-space: nowrap;
.member-item { .member-name {
width: 120rpx; width: 100%;
display: flex; flex: 1;
flex-direction: column; overflow: hidden;
margin: 8rpx 2rpx; text-align: center;
position: relative;
align-items: center;
padding-right: 5px;
white-space: nowrap; white-space: nowrap;
padding-top: 8rpx;
.member-name { font-size: $im-font-size-smaller;
width: 100%;
flex: 1;
overflow: hidden;
text-align: center;
white-space: nowrap;
padding-top: 8rpx;
font-size: $im-font-size-smaller;
}
}
.invite-btn {
display: flex;
justify-content: center;
align-items: center;
width: 86rpx;
height: 86rpx;
margin: 10rpx;
border: $im-border solid 2rpx;
border-radius: 10%;
} }
} }
.member-more { .invite-btn {
padding-top: 24rpx; display: flex;
text-align: center; justify-content: center;
font-size: $im-font-size-small; align-items: center;
color: $im-text-color-lighter; width: 86rpx;
height: 86rpx;
margin: 10rpx;
border: $im-border solid 2rpx;
border-radius: 10%;
} }
} }
.member-more {
padding-top: 24rpx;
text-align: center;
font-size: $im-font-size-small;
color: $im-text-color-lighter;
}
}
.group-detail {
margin-top: 30rpx; .group-detail {
padding: 20rpx 20rpx; margin-top: 30rpx;
background: white; padding: 20rpx 20rpx;
background: white;
.detail-text{
font-size: $im-font-size; .detail-text {
} font-size: $im-font-size;
}
.group-edit {
padding-top: 30rpx; .group-edit {
text-align: center; padding-top: 30rpx;
font-size: $im-font-size-small; text-align: center;
color: $im-text-color-lighter; font-size: $im-font-size-small;
} color: $im-text-color-lighter;
} }
} }
}
</style> </style>

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

@ -1,166 +1,165 @@
<template> <template>
<view class="page group-invite"> <view class="page group-invite">
<view class="nav-bar"> <view class="nav-bar">
<view class="nav-search"> <view class="nav-search">
<uni-search-bar v-model="searchText" radius="100" cancelButton="none" placeholder="输入好友昵称搜索"></uni-search-bar> <uni-search-bar v-model="searchText" radius="100" cancelButton="none"
</view> placeholder="输入好友昵称搜索"></uni-search-bar>
</view> </view>
</view>
<view class="friend-items"> <view class="friend-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true"> <scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="friend in friendItems" v-show="!searchText || friend.nickName.includes(searchText)" <view v-for="friend in friendItems" v-show="!searchText || friend.nickName.includes(searchText)"
:key="friend.id"> :key="friend.id">
<view class="friend-item" @click="onSwitchChecked(friend)" :class="{checked: friend.checked, disabled: friend.disabled}"> <view class="friend-item" @click="onSwitchChecked(friend)"
<head-image :name="friend.nickName" :class="{ checked: friend.checked, disabled: friend.disabled }">
:online="friend.online" :url="friend.headImage"></head-image> <head-image :name="friend.nickName" :online="friend.online"
:url="friend.headImage"></head-image>
<view class="friend-name">{{ friend.nickName}}</view> <view class="friend-name">{{ friend.nickName }}</view>
<!-- <view class="friend-checked">-->
<!-- <radio :checked="friend.checked" :disabled="friend.disabled" @click.stop="onSwitchChecked(friend)"/>-->
<!-- </view>-->
</view> </view>
</view> </view>
</scroll-view> </scroll-view>
</view> </view>
<button class="bottom-btn" type="primary" :disabled="inviteSize==0" @click="onInviteFriends()">邀请({{inviteSize}}) </button> <button class="bottom-btn" type="primary" :disabled="inviteSize == 0"
@click="onInviteFriends()">邀请({{ inviteSize }}) </button>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
groupId: null, groupId: null,
searchText: "", searchText: "",
groupMembers: [], groupMembers: [],
friendItems: [] friendItems: []
}
},
methods: {
onInviteFriends() {
let inviteVo = {
groupId: this.groupId,
friendIds: []
} }
}, this.friendItems.forEach((f) => {
methods: { if (f.checked && !f.disabled) {
onInviteFriends() { inviteVo.friendIds.push(f.id);
let inviteVo = {
groupId: this.groupId,
friendIds: []
}
this.friendItems.forEach((f) => {
if (f.checked && !f.disabled) {
inviteVo.friendIds.push(f.id);
}
})
if (inviteVo.friendIds.length > 0) {
this.$http({
url: "/group/invite",
method: 'POST',
data: inviteVo
}).then(() => {
uni.showToast({
title: "邀请成功",
icon: 'none'
})
setTimeout(() => {
// 退
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.$vm.loadGroupMembers();
uni.navigateBack();
}, 1000);
})
} }
}, })
onShowUserInfo(userId) { if (inviteVo.friendIds.length > 0) {
uni.navigateTo({
url: "/pages/common/user-info?id=" + userId
})
},
onSwitchChecked(friend) {
if (!friend.disabled) {
friend.checked = !friend.checked;
}
},
initFriendItems() {
this.friendItems = [];
let friends = this.friendStore.friends;
friends.forEach((f => {
let item = {
id: f.id,
headImage: f.headImage,
nickName: f.nickName,
online: f.online
}
item.disabled = this.isGroupMember(f.id);
item.checked = item.disabled;
this.friendItems.push(item);
}))
},
loadGroupMembers(id) {
this.$http({ this.$http({
url: `/group/members/${id}`, url: "/group/invite",
method: "GET" method: 'POST',
}).then((members) => { data: inviteVo
this.groupMembers = members.filter(m => !m.quit); }).then(() => {
this.initFriendItems(); uni.showToast({
}) title: "邀请成功",
}, icon: 'none'
})
setTimeout(() => {
// 退
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.$vm.loadGroupMembers();
uni.navigateBack();
}, 1000);
isGroupMember(id) { })
return this.groupMembers.some(m => m.userId == id);
} }
}, },
computed: { onShowUserInfo(userId) {
inviteSize() { uni.navigateTo({
return this.friendItems.filter(f => !f.disabled && f.checked).length; url: "/pages/common/user-info?id=" + userId
})
},
onSwitchChecked(friend) {
if (!friend.disabled) {
friend.checked = !friend.checked;
} }
}, },
onLoad(options) { initFriendItems() {
this.groupId = options.id; this.friendItems = [];
this.loadGroupMembers(options.id); let friends = this.friendStore.friends;
friends.forEach((f => {
let item = {
id: f.id,
headImage: f.headImage,
nickName: f.nickName,
online: f.online
}
item.disabled = this.isGroupMember(f.id);
item.checked = item.disabled;
this.friendItems.push(item);
}))
},
loadGroupMembers(id) {
this.$http({
url: `/group/members/${id}`,
method: "GET"
}).then((members) => {
this.groupMembers = members.filter(m => !m.quit);
this.initFriendItems();
})
},
isGroupMember(id) {
return this.groupMembers.some(m => m.userId == id);
}
},
computed: {
inviteSize() {
return this.friendItems.filter(f => !f.disabled && f.checked).length;
} }
},
onLoad(options) {
this.groupId = options.id;
this.loadGroupMembers(options.id);
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.group-invite { .group-invite {
position: relative;
display: flex;
flex-direction: column;
.friend-items {
position: relative; position: relative;
display: flex; flex: 1;
flex-direction: column; overflow: hidden;
.friend-items { .friend-item {
height: 120rpx;
display: flex;
margin-bottom: 1rpx;
position: relative; position: relative;
flex: 1; padding: 0 30rpx;
overflow: hidden; align-items: center;
background-color: white;
.friend-item { white-space: nowrap;
height: 120rpx;
display: flex;
margin-bottom: 1rpx;
position: relative;
padding: 0 30rpx ;
align-items: center;
background-color: white;
white-space: nowrap;
&.disabled { &.disabled {
background-color: $im-bg-active !important; background-color: $im-bg-active !important;
} }
&.checked {
background-color: $im-color-primary-light-9;
}
.friend-name { &.checked {
flex:1; background-color: $im-color-primary-light-9;
padding-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
}
} }
.scroll-bar { .friend-name {
height: 100%; flex: 1;
padding-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
} }
} }
.scroll-bar {
height: 100%;
}
} }
}
</style> </style>

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

@ -1,27 +1,27 @@
<template> <template>
<view class="page group-member"> <view class="page group-member">
<nav-bar back>群成员</nav-bar> <nav-bar back>群成员</nav-bar>
<view class="nav-bar"> <view class="nav-bar">
<view class="nav-search"> <view class="nav-search">
<uni-search-bar v-model="searchText" radius="100" cancelButton="none" placeholder="输入昵称搜索"></uni-search-bar> <uni-search-bar v-model="searchText" radius="100" cancelButton="none"
</view> placeholder="输入昵称搜索"></uni-search-bar>
</view> </view>
</view>
<view class="member-items"> <view class="member-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true"> <scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="(member,idx) in groupMembers" <view v-for="(member, idx) in groupMembers"
v-show="!searchText || member.showNickName.includes(searchText)" :key="idx"> v-show="!searchText || member.showNickName.includes(searchText)" :key="idx">
<view class="member-item" @click="onShowUserInfo(member.userId)"> <view class="member-item" @click="onShowUserInfo(member.userId)">
<head-image :name="member.showNickName" :online="member.online" :url="member.headImage"></head-image> <head-image :name="member.showNickName" :online="member.online"
<view class="member-name">{{ member.showNickName}} :url="member.headImage"></head-image>
<uni-tag v-if="member.userId==group.ownerId" <view class="member-name">{{ member.showNickName }}
text="群主" size="small" circle type="error"> <uni-tag v-if="member.userId == group.ownerId" text="群主" size="small" circle type="error">
</uni-tag> </uni-tag>
<uni-tag v-if="member.userId==userStore.userInfo.id" <uni-tag v-if="member.userId == userStore.userInfo.id" text="我" size="small" circle></uni-tag>
text="我" size="small" circle></uni-tag>
</view> </view>
<view class="member-kick"> <view class="member-kick">
<button type="warn" plain v-show="isOwner && !isSelf(member.userId)" size="mini" <button type="warn" plain v-show="isOwner && !isSelf(member.userId)" size="mini"
@click.stop="onKickOut(member,idx)">移出群聊</button> @click.stop="onKickOut(member, idx)">移出群聊</button>
</view> </view>
</view> </view>
</view> </view>
@ -31,128 +31,128 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
isModify: false, isModify: false,
searchText: "", searchText: "",
group: {}, group: {},
groupMembers: [] groupMembers: []
} }
},
methods: {
onShowUserInfo(userId) {
uni.navigateTo({
url: "/pages/common/user-info?id=" + userId
})
}, },
methods: { onKickOut(member, idx) {
onShowUserInfo(userId) { uni.showModal({
uni.navigateTo({ title: '确认移出?',
url: "/pages/common/user-info?id=" + userId content: `确定将成员'${member.showNickName}'移出群聊吗?`,
}) success: (res) => {
}, if (res.cancel)
onKickOut(member, idx) { return;
uni.showModal({ this.$http({
title: '确认移出?', url: `/group/kick/${this.group.id}?userId=${member.userId}`,
content: `确定将成员'${member.showNickName}'移出群聊吗?`, method: 'DELETE'
success: (res) => { }).then(() => {
if (res.cancel) uni.showToast({
return; title: `已将${member.showNickName}移出群聊`,
this.$http({ icon: 'none'
url: `/group/kick/${this.group.id}?userId=${member.userId}`, })
method: 'DELETE' this.groupMembers.splice(idx, 1);
}).then(() => { this.isModify = true;
uni.showToast({ });
title: `已将${member.showNickName}移出群聊`, }
icon: 'none' })
})
this.groupMembers.splice(idx, 1);
this.isModify = true;
});
}
})
},
loadGroupInfo(id) {
this.$http({
url: `/group/find/${id}`,
method: 'GET'
}).then((group) => {
this.group = group;
});
},
loadGroupMembers(id) {
this.$http({
url: `/group/members/${id}`,
method: "GET"
}).then((members) => {
this.groupMembers = members.filter(m => !m.quit);
})
},
isSelf(userId) {
return this.userStore.userInfo.id == userId
}
}, },
computed: { loadGroupInfo(id) {
isOwner() { this.$http({
return this.userStore.userInfo.id == this.group.ownerId; url: `/group/find/${id}`,
} method: 'GET'
}).then((group) => {
this.group = group;
});
}, },
onLoad(options) { loadGroupMembers(id) {
this.loadGroupInfo(options.id); this.$http({
this.loadGroupMembers(options.id); url: `/group/members/${id}`,
method: "GET"
}).then((members) => {
this.groupMembers = members.filter(m => !m.quit);
})
}, },
onUnload() { isSelf(userId) {
if (this.isModify) { return this.userStore.userInfo.id == userId
// }
let pages = getCurrentPages(); },
let prevPage = pages[pages.length - 2]; computed: {
prevPage.$vm.loadGroupMembers(); isOwner() {
} return this.userStore.userInfo.id == this.group.ownerId;
}
},
onLoad(options) {
this.loadGroupInfo(options.id);
this.loadGroupMembers(options.id);
},
onUnload() {
if (this.isModify) {
//
let pages = getCurrentPages();
let prevPage = pages[pages.length - 2];
prevPage.$vm.loadGroupMembers();
} }
} }
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.group-member { .group-member {
position: relative;
display: flex;
flex-direction: column;
.member-items {
position: relative; position: relative;
display: flex; flex: 1;
flex-direction: column; overflow: hidden;
.member-items { .member-item {
height: 120rpx;
display: flex;
margin-bottom: 1rpx;
position: relative; position: relative;
flex: 1; padding: 0 30rpx;
overflow: hidden; align-items: center;
background-color: white;
white-space: nowrap;
.member-item { .member-name {
height: 120rpx;
display: flex; display: flex;
margin-bottom: 1rpx;
position: relative;
padding: 0 30rpx ;
align-items: center; align-items: center;
background-color: white; flex: 1;
padding-left: 20rpx;
font-size: $im-font-size;
line-height: $im-font-size * 2;
white-space: nowrap; white-space: nowrap;
overflow: hidden;
.member-name {
display: flex; .uni-tag {
align-items: center; margin-left: 5rpx;
flex:1; width: 40rpx;
padding-left: 20rpx; border: 0;
font-size: $im-font-size; height: 30rpx;
line-height: $im-font-size * 2; line-height: 30rpx;
white-space: nowrap; font-size: 20rpx;
overflow: hidden; text-align: center;
.uni-tag {
margin-left: 5rpx;
width: 40rpx;
border: 0;
height: 30rpx;
line-height: 30rpx;
font-size: 20rpx;
text-align: center;
}
} }
} }
}
.scroll-bar { .scroll-bar {
height: 100%; height: 100%;
}
} }
} }
}
</style> </style>

79
im-uniapp/pages/group/group.vue

@ -1,19 +1,19 @@
<template> <template>
<view class="tab-page group"> <view class="tab-page group">
<nav-bar add search @add="onCreateNewGroup" @search="showSearch = !showSearch">群聊</nav-bar> <nav-bar add search @add="onCreateNewGroup" @search="showSearch = !showSearch">群聊</nav-bar>
<view class="nav-bar" v-if="showSearch"> <view class="nav-bar" v-if="showSearch">
<view class="nav-search"> <view class="nav-search">
<uni-search-bar v-model="searchText" cancelButton="none" radius="100" <uni-search-bar v-model="searchText" cancelButton="none" radius="100"
placeholder="点击搜索群聊"></uni-search-bar> placeholder="点击搜索群聊"></uni-search-bar>
</view> </view>
</view> </view>
<view class="group-tip" v-if="groupStore.groups.length==0"> <view class="group-tip" v-if="groupStore.groups.length == 0">
温馨提示您现在还没有加入任何群聊点击右上方'+'按钮可以创建群聊哦~ 温馨提示您现在还没有加入任何群聊点击右上方'+'按钮可以创建群聊哦~
</view> </view>
<view class="group-items" v-else> <view class="group-items" v-else>
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true"> <scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="group in groupStore.groups" :key="group.id"> <view v-for="group in groupStore.groups" :key="group.id">
<group-item v-if="!group.quit&&group.showGroupName.includes(searchText)" <group-item v-if="!group.quit && group.showGroupName.includes(searchText)"
:group="group"></group-item> :group="group"></group-item>
</view> </view>
</scroll-view> </scroll-view>
@ -22,52 +22,51 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
showSearch: false, showSearch: false,
searchText: "" searchText: ""
}
},
methods: {
onFocusSearch() {
},
onCreateNewGroup() {
uni.navigateTo({
url: "/pages/group/group-edit"
})
}
} }
},
methods: {
onFocusSearch() {
},
onCreateNewGroup() {
uni.navigateTo({
url: "/pages/group/group-edit"
})
}
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.group { .group {
position: relative; position: relative;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.group-tip { .group-tip {
position: absolute; position: absolute;
top: 400rpx; top: 400rpx;
padding: 50rpx; padding: 50rpx;
text-align: left; text-align: left;
line-height: 50rpx; line-height: 50rpx;
color: darkblue; color: darkblue;
font-size: 30rpx; font-size: 30rpx;
} }
.group-items { .group-items {
flex: 1; flex: 1;
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;
position: relative; position: relative;
.scroll-bar { .scroll-bar {
height: 100%; height: 100%;
}
} }
} }
}
</style> </style>

136
im-uniapp/pages/login/login.vue

@ -6,7 +6,7 @@
<uni-easyinput type="text" v-model="loginForm.userName" prefix-icon="person" placeholder="用户名" /> <uni-easyinput type="text" v-model="loginForm.userName" prefix-icon="person" placeholder="用户名" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item name="password"> <uni-forms-item name="password">
<uni-easyinput type="password" v-model="loginForm.password" prefix-icon="locked" placeholder="密码" /> <uni-easyinput type="password" v-model="loginForm.password" prefix-icon="locked" placeholder="密码" />
</uni-forms-item> </uni-forms-item>
<button class="btn-submit" @click="submit" type="primary">登录</button> <button class="btn-submit" @click="submit" type="primary">登录</button>
</uni-forms> </uni-forms>
@ -17,85 +17,85 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
loginForm: { loginForm: {
terminal: 1, // APP terminal: 1, // APP
userName: '', userName: '',
password: '' password: ''
},
rules: {
userName: {
rules: [{
required: true,
errorMessage: '请输入用户名',
}]
}, },
rules: { password: {
userName: { rules: [{
rules: [{ required: true,
required: true, errorMessage: '请输入密码',
errorMessage: '请输入用户名', }]
}]
},
password: {
rules: [{
required: true,
errorMessage: '请输入密码',
}]
}
} }
} }
}, }
methods: { },
submit() { methods: {
this.$http({ submit() {
url: '/login', this.$http({
data: this.loginForm, url: '/login',
method: 'POST' data: this.loginForm,
}).then(loginInfo => { method: 'POST'
console.log("登录成功,自动跳转到聊天页面...") }).then(loginInfo => {
uni.setStorageSync("userName", this.loginForm.userName); console.log("登录成功,自动跳转到聊天页面...")
uni.setStorageSync("password", this.loginForm.password); uni.setStorageSync("userName", this.loginForm.userName);
uni.setStorageSync("loginInfo", loginInfo); uni.setStorageSync("password", this.loginForm.password);
// App.vue uni.setStorageSync("loginInfo", loginInfo);
getApp().$vm.init() // App.vue
// getApp().$vm.init()
uni.switchTab({ //
url: "/pages/chat/chat" uni.switchTab({
}) url: "/pages/chat/chat"
}) })
} })
},
onLoad() {
this.loginForm.userName = uni.getStorageSync("userName");
this.loginForm.password = uni.getStorageSync("password");
} }
},
onLoad() {
this.loginForm.userName = uni.getStorageSync("userName");
this.loginForm.password = uni.getStorageSync("password");
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.login { .login {
.title { .title {
padding-top: 150rpx; padding-top: 150rpx;
padding-bottom: 50rpx; padding-bottom: 50rpx;
color: $im-color-primary; color: $im-color-primary;
text-align: center; text-align: center;
font-size: 24px; font-size: 24px;
font-weight: bold; font-weight: bold;
} }
.form { .form {
padding: 50rpx; padding: 50rpx;
.btn-submit { .btn-submit {
margin-top: 80rpx; margin-top: 80rpx;
border-radius: 50rpx; border-radius: 50rpx;
}
} }
}
.nav-register { .nav-register {
position: fixed; position: fixed;
width: 100%; width: 100%;
bottom: 100rpx; bottom: 100rpx;
color: $im-color-primary; color: $im-color-primary;
text-align: center; text-align: center;
font-size: $im-font-size; font-size: $im-font-size;
}
} }
}
</style> </style>

128
im-uniapp/pages/mine/mine-edit.vue

@ -1,81 +1,81 @@
<template> <template>
<view class="page mine-edit"> <view class="page mine-edit">
<nav-bar back>修改我的信息</nav-bar> <nav-bar back>修改我的信息</nav-bar>
<uni-card :is-shadow="false" is-full :border="false"> <uni-card :is-shadow="false" is-full :border="false">
<uni-forms ref="form" :modelValue="userInfo" label-position="top" <uni-forms ref="form" :modelValue="userInfo" label-position="top" label-width="100%">
label-width="100%"> <uni-forms-item name="headImage" class="avatar">
<uni-forms-item name="headImage" class="avatar"> <image-upload :onSuccess="onUnloadImageSuccess">
<image-upload :onSuccess="onUnloadImageSuccess"> <image :src="userInfo.headImageThumb" class="head-image"></image>
<image :src="userInfo.headImageThumb" class="head-image"></image> </image-upload>
</image-upload> </uni-forms-item>
</uni-forms-item> <uni-forms-item label="用户名" name="userName">
<uni-forms-item label="用户名" name="userName"> <uni-easyinput type="text" v-model="userInfo.userName" :disabled="true" />
<uni-easyinput type="text" v-model="userInfo.userName" :disabled="true" /> </uni-forms-item>
</uni-forms-item> <uni-forms-item label="昵称" name="nickName">
<uni-forms-item label="昵称" name="nickName"> <uni-easyinput v-model="userInfo.nickName" type="text" :placeholder="userInfo.userName" />
<uni-easyinput v-model="userInfo.nickName" type="text" :placeholder="userInfo.userName" /> </uni-forms-item>
</uni-forms-item> <uni-forms-item label="性别" name="sex">
<uni-forms-item label="性别" name="sex"> <uni-data-checkbox v-model="userInfo.sex"
<uni-data-checkbox v-model="userInfo.sex" :localdata="[{text: '男', value: 0}, {text: '女', value: 1}]"></uni-data-checkbox> :localdata="[{ text: '男', value: 0 }, { text: '女', value: 1 }]"></uni-data-checkbox>
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="签名" name="signature"> <uni-forms-item label="签名" name="signature">
<uni-easyinput type="textarea" v-model="userInfo.signature" placeholder="编辑个性标签,展示我的独特态度" /> <uni-easyinput type="textarea" v-model="userInfo.signature" placeholder="编辑个性标签,展示我的独特态度" />
</uni-forms-item> </uni-forms-item>
</uni-forms> </uni-forms>
</uni-card> </uni-card>
<button type="primary" class="bottom-btn" @click="onSubmit()">提交</button> <button type="primary" class="bottom-btn" @click="onSubmit()">提交</button>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
userInfo: {} userInfo: {}
} }
},
methods: {
onSexchange(e) {
this.userInfo.sex = e.detail.value;
}, },
methods:{ onUnloadImageSuccess(file, res) {
onSexchange(e){ this.userInfo.headImage = res.data.originUrl;
this.userInfo.sex=e.detail.value; this.userInfo.headImageThumb = res.data.thumbUrl;
},
onUnloadImageSuccess(file, res) {
this.userInfo.headImage = res.data.originUrl;
this.userInfo.headImageThumb = res.data.thumbUrl;
},
onSubmit(){
this.$http({
url: "/user/update",
method: "PUT",
data: this.userInfo
}).then(()=>{
this.userStore.setUserInfo(this.userInfo);
uni.showToast({
title:"修改成功",
icon: 'none'
});
setTimeout(()=>{
uni.navigateBack();
},1000);
})
}
}, },
onLoad() { onSubmit() {
// this.$http({
let mine = this.userStore.userInfo; url: "/user/update",
this.userInfo = JSON.parse(JSON.stringify(mine)); method: "PUT",
data: this.userInfo
}).then(() => {
this.userStore.setUserInfo(this.userInfo);
uni.showToast({
title: "修改成功",
icon: 'none'
});
setTimeout(() => {
uni.navigateBack();
}, 1000);
})
} }
},
onLoad() {
//
let mine = this.userStore.userInfo;
this.userInfo = JSON.parse(JSON.stringify(mine));
} }
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.mine-edit { .mine-edit {
.head-image { .head-image {
width: 200rpx; width: 200rpx;
height: 200rpx; height: 200rpx;
}
} }
}
.avatar { .avatar {
margin-top: -30px; margin-top: -30px;
} }
</style> </style>

165
im-uniapp/pages/mine/mine-password.vue

@ -1,102 +1,101 @@
<template> <template>
<view class="page mine-password"> <view class="page mine-password">
<nav-bar back>修改密码</nav-bar> <nav-bar back>修改密码</nav-bar>
<uni-card :is-shadow="false" is-full :border="false"> <uni-card :is-shadow="false" is-full :border="false">
<uni-forms ref="form" :modelValue="formData" label-position="top" label-width="100%" > <uni-forms ref="form" :modelValue="formData" label-position="top" label-width="100%">
<uni-forms-item label="原密码" name="oldPassword"> <uni-forms-item label="原密码" name="oldPassword">
<uni-easyinput type="password" v-model="formData.oldPassword" /> <uni-easyinput type="password" v-model="formData.oldPassword" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="新密码" name="newPassword"> <uni-forms-item label="新密码" name="newPassword">
<uni-easyinput type="password" v-model="formData.newPassword" /> <uni-easyinput type="password" v-model="formData.newPassword" />
</uni-forms-item> </uni-forms-item>
<uni-forms-item label="确认密码" name="confirmPassword"> <uni-forms-item label="确认密码" name="confirmPassword">
<uni-easyinput type="password" v-model="formData.confirmPassword" /> <uni-easyinput type="password" v-model="formData.confirmPassword" />
</uni-forms-item> </uni-forms-item>
</uni-forms> </uni-forms>
</uni-card> </uni-card>
<button class="bottom-btn" type="primary" @click="onSubmit()">提交</button> <button class="bottom-btn" type="primary" @click="onSubmit()">提交</button>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
formData: { formData: {
oldPassword: "", oldPassword: "",
newPassword: "", newPassword: "",
confirmPassword: "" confirmPassword: ""
},
rules: {
oldPassword: {
rules: [{
required: true,
errorMessage: '请输入原密码',
}]
}, },
rules: { newPassword: {
oldPassword: { rules: [{
rules: [{ required: true,
required: true, errorMessage: '请输入新密码',
errorMessage: '请输入原密码', }, {
}] validateFunction: function (rule, value, data, callback) {
}, if (data.confirmPassword != data.newPassword) {
newPassword: { callback("两次输入的密码不一致");
rules: [{
required: true,
errorMessage: '请输入新密码',
}, {
validateFunction: function(rule, value, data, callback) {
if (data.confirmPassword != data.newPassword) {
callback("两次输入的密码不一致");
}
if (data.newPassword == data.oldPassword) {
callback("新密码不能和原密码一致");
}
return true;
} }
}] if (data.newPassword == data.oldPassword) {
}, callback("新密码不能和原密码一致");
confirmPassword: { }
rules: [{ return true;
required: true, }
errorMessage: '请输入确认密码', }]
}, { },
validateFunction: function(rule, value, data, callback) { confirmPassword: {
if (data.confirmPassword != data.newPassword) { rules: [{
callback("两次输入的密码不一致"); required: true,
} errorMessage: '请输入确认密码',
}, {
return true; validateFunction: function (rule, value, data, callback) {
if (data.confirmPassword != data.newPassword) {
callback("两次输入的密码不一致");
} }
}]
}
return true;
}
}]
} }
} }
}, }
methods: { },
onSubmit() { methods: {
this.$refs.form.validate().then(res => { onSubmit() {
this.$http({ this.$refs.form.validate().then(res => {
url: "/modifyPwd", this.$http({
method: "PUT", url: "/modifyPwd",
data: this.formData method: "PUT",
}).then((res) => { data: this.formData
uni.showToast({ }).then((res) => {
title: "修改密码成功", uni.showToast({
icon: 'none' title: "修改密码成功",
}) icon: 'none'
setTimeout(()=>{
uni.navigateBack();
},1000);
}) })
}).catch(err => { setTimeout(() => {
console.log('表单错误信息:', err); uni.navigateBack();
}, 1000);
}) })
}).catch(err => {
console.log('表单错误信息:', err);
})
}
},
onReady() {
// onReady
this.$refs.form.setRules(this.rules)
} }
},
onReady() {
// onReady
this.$refs.form.setRules(this.rules)
} }
}
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss"></style>
</style>

236
im-uniapp/pages/mine/mine.vue

@ -1,45 +1,41 @@
<template> <template>
<view class="page mine"> <view class="page mine">
<nav-bar>我的</nav-bar> <nav-bar>我的</nav-bar>
<uni-card :is-shadow="false" is-full :border="false"> <uni-card :is-shadow="false" is-full :border="false">
<view class="content" @click="onModifyInfo()"> <view class="content" @click="onModifyInfo()">
<head-image :name="userInfo.nickName" <head-image :name="userInfo.nickName" :url="userInfo.headImage" :size="160"></head-image>
:url="userInfo.headImage" <view class="info-item">
:size="160"></head-image> <view class="info-primary">
<view class="info-item"> <text class="info-username">
<view class="info-primary"> {{ userInfo.userName }}
<text class="info-username"> </text>
{{userInfo.userName}} <text v-show="userInfo.sex == 0" class="iconfont icon-man" color="darkblue"></text>
</text> <text v-show="userInfo.sex == 1" class="iconfont icon-girl" color="darkred"></text>
<text v-show="userInfo.sex==0" class="iconfont icon-man" </view>
color="darkblue"></text> <view class="info-text">
<text v-show="userInfo.sex==1" class="iconfont icon-girl" <text class="label-text">
color="darkred"></text> 昵称:
</view> </text>
<view class="info-text"> <text class="content-text">
<text class="label-text"> {{ userInfo.nickName }}
昵称: </text>
</text> </view>
<text class="content-text"> <view class="info-text">
{{userInfo.nickName}} <view>
</text> <text class="label-text">
</view> 签名:
<view class="info-text"> </text>
<view> <text class="content-text">
<text class="label-text"> {{ userInfo.signature }}
签名: </text>
</text> </view>
<text class="content-text"> </view>
{{userInfo.signature}} </view>
</text> <view class="info-arrow">
</view>
</view> </view>
</view> </view>
<view class="info-arrow"> </uni-card>
</view>
</view>
</uni-card>
<bar-group> <bar-group>
<arrow-bar title="修改密码" @click="onModifyPassword()"></arrow-bar> <arrow-bar title="修改密码" @click="onModifyPassword()"></arrow-bar>
</bar-group> </bar-group>
@ -50,102 +46,104 @@
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return {} return {}
},
methods: {
onModifyInfo() {
uni.navigateTo({
url: "/pages/mine/mine-edit"
})
}, },
methods: { onModifyPassword() {
onModifyInfo() { uni.navigateTo({
uni.navigateTo({ url: "/pages/mine/mine-password"
url: "/pages/mine/mine-edit" })
})
},
onModifyPassword() {
uni.navigateTo({
url: "/pages/mine/mine-password"
})
},
onQuit() {
uni.showModal({
title: '确认退出?',
success: (res) => {
if (res.confirm) {
console.log(getApp())
getApp().$vm.exit()
}
}
});
}
}, },
computed: { onQuit() {
userInfo() { uni.showModal({
return this.userStore.userInfo; title: '确认退出?',
} success: (res) => {
if (res.confirm) {
console.log(getApp())
getApp().$vm.exit()
}
}
});
}
},
computed: {
userInfo() {
return this.userStore.userInfo;
} }
}
} }
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.mine { .mine {
.content { .content {
//height: 200rpx; //height: 200rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
.info-item {
display: flex; display: flex;
align-items: center; align-items: flex-start;
justify-content: space-between; flex-direction: column;
padding: 20rpx; padding-left: 40rpx;
flex: 1;
.info-text {
line-height: 1.5;
//margin-bottom: 10rpx;
}
.label-text {
font-size: $im-font-size-small;
color: $im-text-color-light;
.info-item { }
.content-text {
font-size: $im-font-size-small;
color: $im-text-color-light;
}
.info-primary {
display: flex; display: flex;
align-items: flex-start; align-items: center;
flex-direction: column; margin-bottom: 10rpx;
padding-left: 40rpx;
flex: 1;
.info-text {
line-height: 1.5;
//margin-bottom: 10rpx;
}
.label-text {
font-size: $im-font-size-small;
color: $im-text-color-light;
}
.content-text {
font-size: $im-font-size-small;
color: $im-text-color-light;
}
.info-primary {
display: flex;
align-items: center;
margin-bottom: 10rpx;
.info-username {
font-size: $im-font-size-large;
font-weight: 600;
}
.icon-man { .info-username {
color: $im-text-color; font-size: $im-font-size-large;
font-size: $im-font-size-large; font-weight: 600;
padding-left: 10rpx; }
}
.icon-girl { .icon-man {
color: darkred; color: $im-text-color;
} font-size: $im-font-size-large;
padding-left: 10rpx;
} }
}
.info-arrow { .icon-girl {
width: 50rpx; color: darkred;
font-size: 30rpx; }
position: relative;
left: 30rpx;
} }
} }
.info-arrow {
width: 50rpx;
font-size: 30rpx;
position: relative;
left: 30rpx;
}
} }
}
</style> </style>

198
im-uniapp/pages/register/register.vue

@ -16,128 +16,128 @@
</uni-forms-item> </uni-forms-item>
<button class="btn-submit" @click="submit" type="primary">注册并登陆</button> <button class="btn-submit" @click="submit" type="primary">注册并登陆</button>
</uni-forms> </uni-forms>
<navigator class="nav-login" url="/pages/login/login" > <navigator class="nav-login" url="/pages/login/login">
返回登陆页面 返回登陆页面
</navigator> </navigator>
</view> </view>
</template> </template>
<script> <script>
export default { export default {
data() { data() {
return { return {
dataForm: { dataForm: {
userName: '', userName: '',
nickName: '', nickName: '',
password: '', password: '',
corfirmPassword: '' corfirmPassword: ''
},
rules: {
userName: {
rules: [{
required: true,
errorMessage: '请输入用户名',
}]
},
nickName: {
rules: [{
required: true,
errorMessage: '请输入昵称',
}]
}, },
rules: { password: {
userName: { rules: [{
rules: [{ required: true,
required: true, errorMessage: '请输入密码',
errorMessage: '请输入用户名', }]
}] },
}, corfirmPassword: {
nickName: { rules: [{
rules: [{ required: true,
required: true, errorMessage: '请输入确认密码',
errorMessage: '请输入昵称', }, {
}] validateFunction: (rule, value, data, callback) => {
}, console.log("validateFunction")
password: { if (data.password != value) {
rules: [{ callback('两次密码输入不一致')
required: true,
errorMessage: '请输入密码',
}]
},
corfirmPassword: {
rules: [{
required: true,
errorMessage: '请输入确认密码',
}, {
validateFunction: (rule, value, data, callback) => {
console.log("validateFunction")
if (data.password != value) {
callback('两次密码输入不一致')
}
return true;
} }
}] return true;
} }
}]
} }
} }
}, }
methods: { },
submit() { methods: {
this.$refs.form.validate().then(() => { submit() {
this.$http({ this.$refs.form.validate().then(() => {
url: '/register',
data: this.dataForm,
method: 'POST'
}).then(() => {
uni.showToast({
title: "注册成功,您已成为盒子IM的用户",
icon: 'none'
})
this.login();
})
})
},
login() {
const loginForm = {
terminal: this.$enums.TERMINAL_TYPE.APP,
userName: this.dataForm.userName,
password: this.dataForm.password
}
this.$http({ this.$http({
url: '/login', url: '/register',
data: loginForm, data: this.dataForm,
method: 'POST' method: 'POST'
}).then((loginInfo) => { }).then(() => {
console.log("登录成功,自动跳转到聊天页面...") uni.showToast({
uni.setStorageSync("userName", loginForm.userName); title: "注册成功,您已成为盒子IM的用户",
uni.setStorageSync("password", loginForm.password); icon: 'none'
uni.setStorageSync("loginInfo", loginInfo);
// App.vue
getApp().init()
//
uni.switchTab({
url: "/pages/chat/chat"
}) })
this.login();
}) })
})
},
login() {
const loginForm = {
terminal: this.$enums.TERMINAL_TYPE.APP,
userName: this.dataForm.userName,
password: this.dataForm.password
} }
this.$http({
url: '/login',
data: loginForm,
method: 'POST'
}).then((loginInfo) => {
console.log("登录成功,自动跳转到聊天页面...")
uni.setStorageSync("userName", loginForm.userName);
uni.setStorageSync("password", loginForm.password);
uni.setStorageSync("loginInfo", loginInfo);
// App.vue
getApp().init()
//
uni.switchTab({
url: "/pages/chat/chat"
})
})
} }
} }
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.register { .register {
.title { .title {
padding-top: 150rpx; padding-top: 150rpx;
padding-bottom: 50rpx; padding-bottom: 50rpx;
color: $im-color-primary; color: $im-color-primary;
text-align: center; text-align: center;
font-size: 24px; font-size: 24px;
font-weight: 600; font-weight: 600;
} }
.form { .form {
padding: 50rpx; padding: 50rpx;
.btn-submit { .btn-submit {
margin-top: 80rpx; margin-top: 80rpx;
border-radius: 50rpx; border-radius: 50rpx;
}
} }
}
.nav-login { .nav-login {
position: fixed; position: fixed;
width: 100%; width: 100%;
bottom: 100rpx; bottom: 100rpx;
color: $im-color-primary; color: $im-color-primary;
text-align: center; text-align: center;
font-size: 32rpx; font-size: 32rpx;
}
} }
}
</style> </style>

32
im-uniapp/store/chatStore.js

@ -86,7 +86,7 @@ export default defineStore('chatStore', {
this.saveToStorage(); this.saveToStorage();
} }
} }
}, },
readedMessage(pos) { readedMessage(pos) {
let chat = this.findChatByFriend(pos.friendId); let chat = this.findChatByFriend(pos.friendId);
@ -99,7 +99,7 @@ export default defineStore('chatStore', {
} }
} }
}) })
if(!chat.stored){ if (!chat.stored) {
this.saveToStorage(); this.saveToStorage();
} }
}, },
@ -293,7 +293,7 @@ export default defineStore('chatStore', {
} }
}, },
refreshChats() { refreshChats() {
if(!cacheChats){ if (!cacheChats) {
return; return;
} }
// 排序 // 排序
@ -316,18 +316,18 @@ export default defineStore('chatStore', {
let key = "chats-app-" + userId; let key = "chats-app-" + userId;
let chatKeys = []; let chatKeys = [];
// 按会话为单位存储,只存储有改动的会话 // 按会话为单位存储,只存储有改动的会话
this.chats.forEach((chat)=>{ this.chats.forEach((chat) => {
let chatKey = `${key}-${chat.type}-${chat.targetId}` let chatKey = `${key}-${chat.type}-${chat.targetId}`
if(!chat.stored){ if (!chat.stored) {
if(chat.delete){ if (chat.delete) {
uni.removeStorageSync(chatKey); uni.removeStorageSync(chatKey);
}else{ } else {
uni.setStorageSync(chatKey,chat); uni.setStorageSync(chatKey, chat);
} }
chat.stored = true; chat.stored = true;
} }
if(!chat.delete){ if (!chat.delete) {
chatKeys.push(chatKey); chatKeys.push(chatKey);
} }
}) })
// 会话核心信息 // 会话核心信息
@ -353,15 +353,15 @@ export default defineStore('chatStore', {
let userStore = useUserStore(); let userStore = useUserStore();
let userId = userStore.userInfo.id; let userId = userStore.userInfo.id;
let chatsData = uni.getStorageSync("chats-app-" + userId) let chatsData = uni.getStorageSync("chats-app-" + userId)
if(chatsData){ if (chatsData) {
if(chatsData.chatKeys){ if (chatsData.chatKeys) {
let time = new Date().getTime(); let time = new Date().getTime();
chatsData.chats = []; chatsData.chats = [];
chatsData.chatKeys.forEach(key=>{ chatsData.chatKeys.forEach(key => {
let chat = uni.getStorageSync(key); let chat = uni.getStorageSync(key);
if(chat){ if (chat) {
chatsData.chats.push(chat); chatsData.chats.push(chat);
} }
}) })
} }
this.initChats(chatsData); this.initChats(chatsData);
@ -375,7 +375,7 @@ export default defineStore('chatStore', {
return state.loadingPrivateMsg || state.loadingGroupMsg return state.loadingPrivateMsg || state.loadingGroupMsg
}, },
curChats: (state) => { curChats: (state) => {
if(cacheChats && state.isLoading()){ if (cacheChats && state.isLoading()) {
return cacheChats; return cacheChats;
} }
return state.chats; return state.chats;

2
im-uniapp/store/userStore.js

@ -14,7 +14,7 @@ export default defineStore('userStore', {
clear() { clear() {
this.userInfo = {}; this.userInfo = {};
}, },
loadUser(context) { loadUser() {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
http({ http({
url: '/user/self', url: '/user/self',

2
im-uniapp/vite.config.js

@ -1,7 +1,5 @@
import { defineConfig } from "vite" import { defineConfig } from "vite"
import uni from "@dcloudio/vite-plugin-uni"; import uni from "@dcloudio/vite-plugin-uni";
const path = require('path')
const fs = require('fs')
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
uni() uni()

1
im-web/src/view/Login.vue

@ -29,7 +29,6 @@
</div> </div>
<icp></icp> <icp></icp>
</div> </div>
</template> </template>
<script> <script>

Loading…
Cancel
Save