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>
import App from './App'
import http from './common/request';
import * as msgType from './common/messageType';
import * as enums from './common/enums';
import * as wsApi from './common/wssocket';
import UNI_APP from '@/.env.js'
import App from './App'
import http from './common/request';
import * as msgType from './common/messageType';
import * as enums from './common/enums';
import * as wsApi from './common/wssocket';
import UNI_APP from '@/.env.js'
export default {
data() {
return {
isExit: false, // 退
audioTip: null,
reconnecting: false //
}
export default {
data() {
return {
isExit: false, // 退
audioTip: null,
reconnecting: false //
}
},
methods: {
init() {
this.isExit = false;
//
this.loadStore().then(() => {
// websocket
this.initWebSocket();
}).catch((e) => {
console.log(e);
this.exit();
})
},
methods: {
init() {
this.isExit = false;
//
this.loadStore().then(() => {
// websocket
this.initWebSocket();
}).catch((e) => {
console.log(e);
this.exit();
})
},
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
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'
})
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',
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 = {
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);
// 线
this.pullPrivateOfflineMessage(this.chatStore.privateMsgMaxId);
this.pullGroupOfflineMessage(this.chatStore.groupMsgMaxId);
});
wsApi.onMessage((cmd, msgInfo) => {
if (cmd == 2) {
// 线
uni.showModal({
content: '您的账号已被管理员封禁,原因:' + msg.content,
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);
}
},
insertGroupMessage(group, msg) {
//
if (msgType.isRtcGroup(msg.type)) {
// #ifdef MP-WEIXIN
//
return;
// #endif
//
let delayTime = 100;
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;
}
});
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;
}
// 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 = {
type: 'GROUP',
targetId: group.id,
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)
})
targetId: msg.groupId
}
},
exit() {
console.log("exit");
this.isExit = true;
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.removeStorageSync("loginInfo");
uni.reLaunch({
url: "/pages/login/login"
uni.showModal({
content: '您的账号已被管理员封禁,原因:' + msg.content,
showCancel: false,
})
this.unloadStore();
},
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;
this.exit();
}
},
insertGroupMessage(group, msg) {
//
if (msgType.isRtcGroup(msg.type)) {
// #ifdef MP-WEIXIN
//
return;
// #endif
//
let delayTime = 100;
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;
}
//
this.reconnecting = true;
// token
this.reloadUserInfo().then((userInfo) => {
uni.showToast({
title: '连接已断开,尝试重新连接...',
icon: 'none',
})
this.userStore.setUserInfo(userInfo);
//
let loginInfo = uni.getStorageSync("loginInfo")
wsApi.reconnect(UNI_APP.WS_URL, loginInfo.accessToken);
}).catch(() => {
// 5s
setTimeout(() => {
this.reconnectWs();
}, 5000)
// chat-group-video
setTimeout(() => {
uni.$emit('WS_RTC_GROUP', msg);
}, delayTime)
return;
}
let chatInfo = {
type: 'GROUP',
targetId: group.id,
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)
})
},
reloadUserInfo() {
return http({
url: '/user/self',
}
},
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)
})
}
},
onLaunch() {
this.$mountStore();
//
let loginInfo = uni.getStorageSync("loginInfo")
this.refreshToken(loginInfo).then(() => {
//
this.init();
//
uni.switchTab({
url: "/pages/chat/chat"
exit() {
console.log("exit");
this.isExit = true;
wsApi.close(3099);
uni.removeStorageSync("loginInfo");
uni.reLaunch({
url: "/pages/login/login"
})
this.unloadStore();
},
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
uni.navigateTo({
url: "/pages/login/login"
})
},
reconnectWs() {
// 退
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>
<style lang="scss">
@import "@/uni_modules/uview-plus/index.scss";
@import "@/im.scss";
@import url('./static/icon/iconfont.css');
@import "@/uni_modules/uview-plus/index.scss";
@import "@/im.scss";
@import url('./static/icon/iconfont.css');
// #ifdef H5
uni-page-head {
display: none; // h5
}
// #endif
// #ifdef H5
uni-page-head {
display: none; // h5
}
.tab-page {
position: relative;
display: flex;
flex-direction: column;
// #ifdef H5
height: calc(100vh - 50px - $im-nav-bar-height); // h5100vh
top: $im-nav-bar-height;
// #endif
// #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;
}
.tab-page {
position: relative;
display: flex;
flex-direction: column;
// #ifdef H5
height: calc(100vh - 50px - $im-nav-bar-height); // h5100vh
top: $im-nav-bar-height;
// #endif
.page {
position: relative;
// #ifdef H5
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;
}
// #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;
}
.page {
position: relative;
// #ifdef H5
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>

18
im-uniapp/common/date.js

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

52
im-uniapp/common/enums.js

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

24
im-uniapp/common/messageType.js

@ -1,32 +1,32 @@
// 是否普通消息
let isNormal = function(type){
return type>=0 && type < 10;
let isNormal = function (type) {
return type >= 0 && type < 10;
}
// 是否状态消息
let isStatus = function(type){
return type>=10 && type < 20;
let isStatus = function (type) {
return type >= 10 && type < 20;
}
// 是否提示消息
let isTip = function(type){
return type>=20 && type < 30;
let isTip = function (type) {
return type >= 20 && type < 30;
}
// 操作交互类消息
let isAction = function(type){
return type>=40 && type < 50;
let isAction = function (type) {
return type >= 40 && type < 50;
}
// 单人通话信令
let isRtcPrivate = function(type){
return type>=100 && type < 200;
let isRtcPrivate = function (type) {
return type >= 100 && type < 200;
}
// 多人通话信令
let isRtcGroup = function(type){
return type>=200 && type < 300;
let isRtcGroup = function (type) {
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 start = () => {
if(rc != null){
if (rc != null) {
close();
}
rc = new Recorder();
@ -22,7 +22,7 @@ let close = () => {
let upload = () => {
return new Promise((resolve, reject) => {
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 file = new File([newbolb], name)
uni.uploadFile({
@ -34,10 +34,10 @@ let upload = () => {
name: 'file',
success: (res) => {
let r = JSON.parse(res.data);
if(r.code != 200){
if (r.code != 200) {
console.log(res)
reject(r.message);
}else {
} else {
const data = {
duration: parseInt(rc.duration),
url: r.data

4
im-uniapp/common/request.js

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

10
im-uniapp/common/wssocket.js

@ -55,7 +55,7 @@ let init = () => {
console.log(e)
isConnect = false;
// 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 delay = timeDiff < 10000 ? 10000 - timeDiff : 0;
rec && clearTimeout(rec);
rec = setTimeout(function() {
rec = setTimeout(function () {
connect(wsurl, accessToken);
}, delay);
};
@ -119,7 +119,7 @@ let close = (code) => {
var heartCheck = {
timeout: 10000, //每段时间发送一次心跳包 这里设置为30s
timeoutObj: null, //延时发送消息对象(启动心跳新建这个对象,收到消息后重置对象)
start: function() {
start: function () {
if (isConnect) {
console.log('发送WebSocket心跳')
let heartBeat = {
@ -134,9 +134,9 @@ var heartCheck = {
})
}
},
reset: function() {
reset: function () {
clearTimeout(this.timeoutObj);
this.timeoutObj = setTimeout(function() {
this.timeoutObj = setTimeout(function () {
heartCheck.start();
}, this.timeout);
}

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

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

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

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

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

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

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

@ -1,61 +1,61 @@
<template>
<view class="switch-bar">
<text class="title">{{title}}</text>
<text class="title">{{ title }}</text>
<switch class="switch" :checked="checked" color="#18bc37" @change="onChange"></switch>
</view>
</template>
<script>
export default {
name: "switch-bar",
props: {
title: {
type: String,
required: true
},
checked: {
type: Boolean,
default: false
}
export default {
name: "switch-bar",
props: {
title: {
type: String,
required: true
},
data() {
return {
value: this.checked
}
},
methods: {
onChange(e) {
this.value = true;
setTimeout(()=>{
this.value = false;
},100)
//this.value = false;
this.$emit('change', e);
}
checked: {
type: Boolean,
default: false
}
},
data() {
return {
value: this.checked
}
},
methods: {
onChange(e) {
this.value = true;
setTimeout(() => {
this.value = false;
}, 100)
//this.value = false;
this.$emit('change', e);
}
}
}
</script>
<style lang="scss" scoped>
.switch-bar {
width: 100%;
height: 100rpx;
font-size: 34rpx;
color: black;
margin-top: 5rpx;
background-color: white;
line-height: 100rpx;
display: flex;
.switch-bar {
width: 100%;
height: 100rpx;
font-size: 34rpx;
color: black;
margin-top: 5rpx;
background-color: white;
line-height: 100rpx;
display: flex;
.title {
flex: 1;
margin-left: 40rpx;
}
.title {
flex: 1;
margin-left: 40rpx;
}
.switch {
margin-right: 40rpx;
}
.switch {
margin-right: 40rpx;
}
}
</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-tip"> 选择要提醒的人</view>
<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>
</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 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>
</view>
</view>
@ -19,15 +19,14 @@
</view>
<view class="member-items">
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<view v-for="m in showMembers" v-show="m.showNickName.includes(searchText)"
:key="m.userId">
<view class="member-item" :class="{checked: m.checked}" @click="onSwitchChecked(m)">
<view v-for="m in showMembers" v-show="m.showNickName.includes(searchText)" :key="m.userId">
<view class="member-item" :class="{ checked: m.checked }" @click="onSwitchChecked(m)">
<head-image :name="m.showNickName" :online="m.online" :url="m.headImage"
size="small"></head-image>
<view class="member-name">{{ m.showNickName}}</view>
<!-- <view class="member-checked">-->
<!-- <radio :checked="m.checked" @click.stop="onSwitchChecked(m)" />-->
<!-- </view>-->
<view class="member-name">{{ m.showNickName }}</view>
<!-- <view class="member-checked">-->
<!-- <radio :checked="m.checked" @click.stop="onSwitchChecked(m)" />-->
<!-- </view>-->
</view>
</view>
</scroll-view>
@ -37,143 +36,143 @@
</template>
<script>
export default {
name: "chat-at-box",
props: {
ownerId: {
type: Number,
},
members: {
type: Array
}
},
data() {
return {
searchText: "",
showMembers:[]
};
export default {
name: "chat-at-box",
props: {
ownerId: {
type: Number,
},
methods: {
init(atUserIds) {
this.showMembers = [];
let userId = this.userStore.userInfo.id;
if(this.ownerId == userId){
this.showMembers.push({
userId:-1,
showNickName: "全体成员"
})
}
this.members.forEach((m) => {
if(!m.quit && m.userId != userId){
m.checked = atUserIds.indexOf(m.userId) >= 0;
this.showMembers.push(m);
}
});
},
open() {
this.$refs.popup.open();
},
onSwitchChecked(member) {
member.checked = !member.checked;
},
onClean() {
this.showMembers.forEach((m) => {
m.checked = false;
members: {
type: Array
}
},
data() {
return {
searchText: "",
showMembers: []
};
},
methods: {
init(atUserIds) {
this.showMembers = [];
let userId = this.userStore.userInfo.id;
if (this.ownerId == userId) {
this.showMembers.push({
userId: -1,
showNickName: "全体成员"
})
},
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: {
atUserIds() {
let ids = [];
this.showMembers.forEach((m) => {
if (m.checked) {
ids.push(m.userId);
}
})
return ids;
open() {
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)
}
}
},
computed: {
atUserIds() {
let ids = [];
this.showMembers.forEach((m) => {
if (m.checked) {
ids.push(m.userId);
}
})
return ids;
}
}
}
</script>
<style lang="scss" scoped>
.chat-at-box {
position: relative;
.chat-at-box {
position: relative;
display: flex;
flex-direction: column;
background-color: white;
padding: 10rpx;
//border-radius: 15rpx;
.chat-at-top {
display: flex;
flex-direction: column;
background-color: white;
align-items: center;
height: 70rpx;
padding: 10rpx;
//border-radius: 15rpx;
.chat-at-top {
display: flex;
align-items: center;
height: 70rpx;
padding: 10rpx;
.chat-at-tip {
flex: 1;
}
.chat-at-tip {
flex: 1;
}
.chat-at-btn {
margin-left: 10rpx;
}
.chat-at-btn {
margin-left: 10rpx;
}
}
.at-user-items {
display: flex;
align-items: center;
height: 90rpx;
.at-user-items {
display: flex;
align-items: center;
height: 90rpx;
.at-user-item {
padding: 3rpx;
}
.at-user-item {
padding: 3rpx;
}
}
.member-items {
.member-items {
position: relative;
flex: 1;
overflow: hidden;
.member-item {
height: 110rpx;
display: flex;
position: relative;
flex: 1;
overflow: hidden;
.member-item {
height: 110rpx;
display: flex;
position: relative;
padding: 0 30rpx;
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;
}
padding: 0 30rpx;
align-items: center;
background-color: white;
white-space: nowrap;
margin-bottom: 1px;
&.checked {
background-color: $im-color-primary-light-9;
}
.scroll-bar {
height: 800rpx;
.member-name {
flex: 1;
padding-left: 20rpx;
font-size: $im-font-size;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;
}
}
.scroll-bar {
height: 800rpx;
}
}
}
</style>

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

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

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

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

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

@ -1,26 +1,26 @@
<template>
<view class="chat-msg-item">
<view class="chat-msg-tip"
v-if="msgInfo.type==$enums.MESSAGE_TYPE.RECALL||msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT">
{{msgInfo.content}}
v-if="msgInfo.type == $enums.MESSAGE_TYPE.RECALL || msgInfo.type == $enums.MESSAGE_TYPE.TIP_TEXT">
{{ msgInfo.content }}
</view>
<view class="chat-msg-tip" v-if="msgInfo.type==$enums.MESSAGE_TYPE.TIP_TIME">
{{$date.toTimeText(msgInfo.sendTime)}}
<view class="chat-msg-tip" v-if="msgInfo.type == $enums.MESSAGE_TYPE.TIP_TIME">
{{ $date.toTimeText(msgInfo.sendTime) }}
</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"
:name="showName" size="small"></head-image>
<view class="chat-msg-content">
<view v-if="msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top">
<text>{{showName}}</text>
<text>{{ showName }}</text>
</view>
<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">
<rich-text class="chat-msg-text" :nodes="$emo.transform(msgInfo.content)"></rich-text>
</pop-menu>
</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">
<view class="img-load-box">
<image class="send-image" mode="widthFix" :src="JSON.parse(msgInfo.content).thumbUrl"
@ -32,13 +32,13 @@
<text title="发送失败" v-if="loadFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></text>
</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">
<view class="chat-file-box">
<view class="chat-file-info">
<uni-link class="chat-file-name" :text="data.name" showUnderLine="true"
color="#007BFF" :href="data.url"></uni-link>
<view class="chat-file-size">{{fileSize}}</view>
<view class="chat-file-size">{{ fileSize }}</view>
</view>
<view class="chat-file-icon iconfont icon-file"></view>
<loading v-if="loading"></loading>
@ -47,32 +47,32 @@
<text title="发送失败" v-if="loadFail" @click="onSendFail"
class="send-fail iconfont icon-warning-circle-fill"></text>
</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()">
<text class="iconfont icon-voice-play"></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=='PLAYING'" class="iconfont icon-pause"></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 == 'PLAYING'" class="iconfont icon-pause"></text>
</view>
</pop-menu>
<pop-menu v-if="isAction" :items="menuItems" @select="onSelectMenu">
<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>
<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>
<text>{{msgInfo.content}}</text>
<text>{{ msgInfo.content }}</text>
</view>
</pop-menu>
<view class="chat-msg-status" v-if="!isAction">
<text class="chat-readed" v-show="msgInfo.selfSend && !msgInfo.groupId
&& msgInfo.status==$enums.MESSAGE_STATUS.READED">已读</text>
<text class="chat-unread" 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
&& msgInfo.status != $enums.MESSAGE_STATUS.READED">未读</text>
</view>
<view class="chat-receipt" v-show="msgInfo.receipt" @click="onShowReadedBox">
<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>
@ -83,404 +83,405 @@
</template>
<script>
export default {
name: "chat-message-item",
props: {
headImage: {
type: String,
required: true
},
showName: {
type: String,
required: true
},
msgInfo: {
type: Object,
required: true
},
groupMembers: {
type: Array
}
export default {
name: "chat-message-item",
props: {
headImage: {
type: String,
required: true
},
data() {
return {
audioPlayState: 'STOP',
innerAudioContext: null,
menu: {
show: false,
style: ""
}
showName: {
type: String,
required: true
},
msgInfo: {
type: Object,
required: true
},
groupMembers: {
type: Array
}
},
data() {
return {
audioPlayState: 'STOP',
innerAudioContext: null,
menu: {
show: false,
style: ""
}
}
},
methods: {
onSendFail() {
uni.showToast({
title: "该文件已发送失败,目前不支持自动重新发送,建议手动重新发送",
icon: "none"
})
},
methods: {
onSendFail() {
uni.showToast({
title: "该文件已发送失败,目前不支持自动重新发送,建议手动重新发送",
icon: "none"
})
},
onPlayAudio() {
//
if (!this.innerAudioContext) {
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]
onPlayAudio() {
//
if (!this.innerAudioContext) {
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"
})
},
onShowReadedBox() {
this.$refs.chatGroupReaded.open();
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"
}
},
computed: {
loading() {
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "loading";
},
loadFail() {
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "fail";
},
data() {
return JSON.parse(this.msgInfo.content)
},
fileSize() {
let size = this.data.size;
if (size > 1024 * 1024) {
return Math.round(size / 1024 / 1024) + "M";
}
if (size > 1024) {
return Math.round(size / 1024) + "KB";
}
return size + "B";
},
menuItems() {
let items = [];
if (this.msgInfo.type == this.$enums.MESSAGE_TYPE.TEXT) {
items.push({
key: 'COPY',
name: '复制',
icon: 'bars'
});
}
if (this.msgInfo.selfSend && this.msgInfo.id > 0) {
items.push({
key: 'RECALL',
name: '撤回',
icon: 'refreshempty'
});
}
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]
})
},
onShowReadedBox() {
this.$refs.chatGroupReaded.open();
}
},
computed: {
loading() {
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "loading";
},
loadFail() {
return this.msgInfo.loadStatus && this.msgInfo.loadStatus === "fail";
},
data() {
return JSON.parse(this.msgInfo.content)
},
fileSize() {
let size = this.data.size;
if (size > 1024 * 1024) {
return Math.round(size / 1024 / 1024) + "M";
}
if (size > 1024) {
return Math.round(size / 1024) + "KB";
}
return size + "B";
},
menuItems() {
let items = [];
if (this.msgInfo.type == this.$enums.MESSAGE_TYPE.TEXT) {
items.push({
key: 'DELETE',
name: '删除',
icon: 'trash',
color: '#e64e4e'
key: 'COPY',
name: '复制',
icon: 'bars'
});
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>
<style scoped lang="scss">
.chat-msg-item {
padding: 2rpx 20rpx;
.chat-msg-tip {
line-height: 60rpx;
text-align: center;
color: $im-text-color-lighter;
font-size: $im-font-size-smaller-extra;
padding: 10rpx;
.chat-msg-item {
padding: 2rpx 20rpx;
.chat-msg-tip {
line-height: 60rpx;
text-align: center;
color: $im-text-color-lighter;
font-size: $im-font-size-smaller-extra;
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 {
position: relative;
margin-bottom: 22rpx;
padding-left: 110rpx;
min-height: 80rpx;
.chat-msg-content {
text-align: left;
.avatar {
position: absolute;
top: 0;
left: 0;
.chat-msg-top {
display: flex;
flex-wrap: nowrap;
color: $im-text-color-lighter;
font-size: $im-font-size-smaller;
line-height: $im-font-size-smaller;
}
.chat-msg-content {
text-align: left;
.chat-msg-bottom {
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;
flex-wrap: nowrap;
color: $im-text-color-lighter;
font-size: $im-font-size-smaller;
line-height: $im-font-size-smaller;
}
.chat-msg-bottom {
display: inline-block;
padding-right: 80rpx;
flex-direction: row;
align-items: center;
.chat-msg-text {
.img-load-box {
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;
.send-image {
min-width: 200rpx;
max-width: 420rpx;
cursor: pointer;
border-radius: 4px;
}
}
.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;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
.img-load-box {
position: relative;
.send-image {
min-width: 200rpx;
max-width: 420rpx;
cursor: pointer;
border-radius: 4px;
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;
}
}
.send-fail {
color: $im-color-danger;
font-size: $im-font-size;
cursor: pointer;
margin: 0 20px;
.chat-file-icon {
font-size: 80rpx;
color: #d42e07;
}
}
.chat-msg-file {
display: flex;
flex-wrap: nowrap;
flex-direction: row;
align-items: center;
.send-fail {
color: #e60c0c;
font-size: 50rpx;
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 {
color: #e60c0c;
font-size: 50rpx;
cursor: pointer;
margin: 0 20rpx;
}
.chat-msg-audio {
display: flex;
align-items: center;
.chat-audio-text {
padding-right: 8px;
}
.chat-msg-audio {
display: flex;
align-items: center;
.icon-voice-play {
font-size: 18px;
padding-right: 8px;
}
}
.chat-audio-text {
padding-right: 8px;
}
.chat-realtime {
display: flex;
align-items: center;
.icon-voice-play {
font-size: 18px;
padding-right: 8px;
}
.iconfont {
font-size: 20px;
padding-right: 8px;
}
}
.chat-realtime {
display: flex;
align-items: center;
.chat-msg-status {
line-height: $im-font-size-smaller-extra;
font-size: $im-font-size-smaller-extra;
padding-top: 2rpx;
.iconfont {
font-size: 20px;
padding-right: 8px;
}
.chat-readed {
display: block;
padding-top: 2rpx;
color: $im-text-color-lighter;
}
.chat-msg-status {
line-height: $im-font-size-smaller-extra;
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-unread {
color: $im-color-danger;
}
}
.chat-receipt {
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
font-weight: 600;
.chat-receipt {
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
font-weight: 600;
.icon-ok {
font-size: 20px;
color: $im-color-success;
}
.icon-ok {
font-size: 20px;
color: $im-color-success;
}
}
}
}
&.chat-msg-mine {
text-align: right;
padding-left: 0;
padding-right: 110rpx;
&.chat-msg-mine {
text-align: right;
padding-left: 0;
padding-right: 110rpx;
.avatar {
left: auto;
right: 0;
}
.avatar {
left: auto;
right: 0;
}
.chat-msg-content {
text-align: right;
.chat-msg-content {
text-align: right;
.chat-msg-bottom {
padding-left: 80rpx;
padding-right: 0;
.chat-msg-bottom {
padding-left: 80rpx;
padding-right: 0;
.chat-msg-text {
margin-left: 10px;
background-color: $im-color-primary-light-2;
color: #fff;
.chat-msg-text {
margin-left: 10px;
background-color: $im-color-primary-light-2;
color: #fff;
&:after {
left: auto;
right: -9px;
border-top-color: $im-color-primary-light-2;
}
&:after {
left: auto;
right: -9px;
border-top-color: $im-color-primary-light-2;
}
}
.chat-msg-image {
flex-direction: row-reverse;
}
.chat-msg-image {
flex-direction: row-reverse;
}
.chat-msg-file {
flex-direction: row-reverse;
}
.chat-msg-file {
flex-direction: row-reverse;
}
.chat-msg-audio {
flex-direction: row-reverse;
.chat-msg-audio {
flex-direction: row-reverse;
.chat-audio-text {
padding-right: 0;
padding-left: 8px;
}
.chat-audio-text {
padding-right: 0;
padding-left: 8px;
}
.icon-voice-play {
transform: rotateY(180deg);
}
.icon-voice-play {
transform: rotateY(180deg);
}
}
.chat-realtime {
display: flex;
flex-direction: row-reverse;
.chat-realtime {
display: flex;
flex-direction: row-reverse;
.iconfont {
transform: rotateY(180deg);
}
.iconfont {
transform: rotateY(180deg);
}
}
}
}
}
}
}
</style>

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

@ -1,8 +1,8 @@
<template>
<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">
{{recording?'正在录音':'长按 说话'}}</view>
{{ recording ? '正在录音' : '长按 说话' }}</view>
<view v-if="recording" class="chat-record-window" :style="recordWindowStyle">
<view class="rc-wave">
<text class="note" style="--d: 0"></text>
@ -13,226 +13,226 @@
<text class="note" style="--d: 5"></text>
<text class="note" style="--d: 6"></text>
</view>
<view class="rc-tip">{{recordTip}}</view>
<view class="rc-tip">{{ recordTip }}</view>
<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 class="opt-tip" :class="moveToCancel?'red':'black'">{{moveToCancel? '松手取消':'松手发送,上划取消'}}</view>
<view class="opt-tip" :class="moveToCancel ? 'red' : 'black'">{{ moveToCancel ? '松手取消' : '松手发送,上划取消' }}</view>
</view>
</view>
</template>
<script>
export default {
name: "chat-record",
data() {
return {
recording: false,
moveToCancel: false,
recordBarTop: 0,
druation: 0,
rcTimer: null
export default {
name: "chat-record",
data() {
return {
recording: false,
moveToCancel: false,
recordBarTop: 0,
druation: 0,
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: {
onTouchMove(e) {
const moveY = e.touches[0].clientY;
this.moveToCancel = moveY < this.recordBarTop - 40;
},
onCancel() {
if (this.recording) {
this.moveToCancel = true;
this.onEndRecord();
}
},
onStartRecord() {
/* 使@touchend
一直处于录音状态这里允许用户再次点击发送语音并结束录音 */
if (this.recording) {
this.onEndRecord();
return;
}
console.log("开始录音")
this.moveToCancel = false;
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"
});
onStartRecord() {
/* 使@touchend
一直处于录音状态这里允许用户再次点击发送语音并结束录音 */
if (this.recording) {
this.onEndRecord();
return;
}
console.log("开始录音")
this.moveToCancel = false;
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;
//
this.$rc.pause();
//
this.StopTimer();
//
if (this.moveToCancel) {
this.$rc.close();
console.log("录音取消")
return;
}
// 1
if (this.druation == 0) {
uni.showToast({
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();
});
},
onEndRecord() {
this.recording = false;
//
this.$rc.pause();
//
this.StopTimer();
//
if (this.moveToCancel) {
this.$rc.close();
console.log("录音取消")
return;
}
// 1
if (this.druation == 0) {
uni.showToast({
title: "说话时间太短",
icon: 'none'
})
},
startTimer() {
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.close();
return;
}
this.$rc.upload().then((data) => {
this.$emit("send", data);
}).catch((e) => {
uni.showToast({
title: e,
icon: 'none'
})
}).finally(() => {
this.$rc.close();
})
},
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后将停止录音`;
startTimer() {
this.druation = 0;
this.StopTimer();
this.rcTimer = setInterval(() => {
this.druation++;
// 60s,
if (this.druation >= 60) {
this.onEndRecord();
}
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>
<style lang="scss" scoped>
.chat-record {
.rc-wave {
display: flex;
align-items: flex-end;
justify-content: center;
position: relative;
height: 80rpx;
.chat-record {
.rc-wave {
display: flex;
align-items: flex-end;
justify-content: center;
position: relative;
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 {
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;
}
100% {
background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
height: 20%;
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 {
padding: 10rpx;
margin: 10rpx;
border-radius: 10rpx;
text-align: center;
box-shadow: $im-box-shadow;
.chat-record-bar {
padding: 10rpx;
margin: 10rpx;
border-radius: 10rpx;
text-align: center;
box-shadow: $im-box-shadow;
&.recording {
background-color: $im-color-primary;
color: #fff;
}
&.recording {
background-color: $im-color-primary;
color: #fff;
}
}
.chat-record-window {
position: fixed;
left: 0;
right: 0;
height: 360rpx;
background-color: rgba(255, 255, 255, 0.95);
padding: 30rpx;
.icon-microphone {
text-align: center;
font-size: 80rpx;
padding: 10rpx;
.chat-record-window {
position: fixed;
left: 0;
right: 0;
height: 360rpx;
background-color: rgba(255, 255, 255, 0.95);
padding: 30rpx;
}
.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 {
text-align: center;
margin-top: 40rpx;
height: 80rpx;
.rc-tip {
text-align: center;
font-size: $im-font-size-small;
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 {
color: $im-color-danger !important;
}
.opt-tip {
text-align: center;
font-size: 30rpx;
padding: 20rpx;
}
.red {
color: $im-color-danger !important;
}
}
}
</style>

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

@ -8,110 +8,109 @@
</template>
<script>
import UNI_APP from '@/.env.js';
import UNI_APP from '@/.env.js';
export default {
name: "file-upload",
data() {
return {
fileMap: new Map(),
option: {
url: UNI_APP.BASE_URL + '/file/upload',
name: 'file',
header: {
accessToken: uni.getStorageSync('loginInfo').accessToken
}
export default {
name: "file-upload",
data() {
return {
fileMap: new Map(),
option: {
url: UNI_APP.BASE_URL + '/file/upload',
name: 'file',
header: {
accessToken: uni.getStorageSync('loginInfo').accessToken
}
}
}
},
props: {
maxSize: {
type: Number,
default: 10
},
props: {
maxSize: {
type: Number,
default: 10
},
onBefore: {
type: Function,
default: null
},
onSuccess: {
type: Function,
default: null
},
onError: {
type: Function,
default: null
}
onBefore: {
type: Function,
default: null
},
methods: {
onUploadEnd(item) {
let file = this.fileMap.get(item.path);
if (item.type == 'fail') {
this.onError(file)
return;
}
let res = JSON.parse(item.responseText);
if (res.code == 200) {
//
this.onOk(file, res);
} else if (res.code == 401) {
// tokentoken
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);
})
} else {
//
onSuccess: {
type: Function,
default: null
},
onError: {
type: Function,
default: null
}
},
methods: {
onUploadEnd(item) {
let file = this.fileMap.get(item.path);
if (item.type == 'fail') {
this.onError(file)
return;
}
let res = JSON.parse(item.responseText);
if (res.code == 200) {
//
this.onOk(file, res);
} else if (res.code == 401) {
// tokentoken
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);
})
} 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) {
return;
}
files.forEach((file, name) => {
if(!this.fileMap.has(file.path)){
this.onBefore && this.onBefore(file)
this.fileMap.set(file.path, file);
})
},
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);
}
})
},
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>
<style>
</style>
<style></style>

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

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

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

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

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

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

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

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

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

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

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

@ -5,89 +5,88 @@
</template>
<script>
import UNI_APP from '@/.env.js'
import UNI_APP from '@/.env.js'
export default {
name: "image-upload",
data() {
return {
uploadHeaders: {
"accessToken": uni.getStorageSync('loginInfo').accessToken
}
export default {
name: "image-upload",
data() {
return {
uploadHeaders: {
"accessToken": uni.getStorageSync('loginInfo').accessToken
}
}
},
props: {
maxCount: {
type: Number,
default: 1
},
props: {
maxCount:{
type: Number,
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
}
maxSize: {
type: Number,
default: 5 * 1024 * 1024
},
methods: {
selectAndUpload() {
uni.chooseImage({
count: this.maxCount, //9
sourceType: [this.sourceType], //album camera 使使
sizeType: ['original'], //original compressed
success: (res) => {
res.tempFiles.forEach((file) => {
console.log("文件:",file)
if (!this.onBefore || this.onBefore(file)) {
//
this.uploadImage(file);
}
})
}
})
},
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);
sourceType: {
type: String,
default: 'album'
},
onBefore: {
type: Function,
default: null
},
onSuccess: {
type: Function,
default: null
},
onError: {
type: Function,
default: null
}
},
methods: {
selectAndUpload() {
uni.chooseImage({
count: this.maxCount, //9
sourceType: [this.sourceType], //album camera 使使
sizeType: ['original'], //original compressed
success: (res) => {
res.tempFiles.forEach((file) => {
console.log("文件:", file)
if (!this.onBefore || this.onBefore(file)) {
//
this.uploadImage(file);
}
},
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>
<style>
</style>
<style></style>

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

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

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

@ -11,9 +11,11 @@
<slot></slot>
</view>
<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="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>
@ -85,8 +87,7 @@ export default {
box-sizing: border-box;
height: $im-nav-bar-height;
.title {
}
.title {}
.back {
position: absolute;

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

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

17
im-uniapp/main.js

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

167
im-uniapp/manifest.json

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

2
im-uniapp/package.json

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

103
im-uniapp/pages.json

@ -1,47 +1,64 @@
{
"easycom": {
"autoscan": true,
"custom": {
"^u--(.*)": "@/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"
}
"autoscan": true,
"custom": {
"^u--(.*)": "@/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"
}
},
"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/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"
},{
"path": "pages/chat/chat-private-video"
},{
"path": "pages/chat/chat-group-video"
}, {
"path": "pages/friend/friend-add"
}, {
"path": "pages/group/group-info"
}, {
"path": "pages/group/group-edit"
}, {
"path": "pages/group/group-invite"
}, {
"path": "pages/group/group-member"
}, {
"path": "pages/mine/mine-edit"
},{
"path": "pages/mine/mine-password"
}
{
"path": "pages/chat/chat-private-video"
},
{
"path": "pages/chat/chat-group-video"
},
{
"path": "pages/friend/friend-add"
},
{
"path": "pages/group/group-info"
},
{
"path": "pages/group/group-edit"
},
{
"path": "pages/group/group-invite"
},
{
"path": "pages/group/group-member"
},
{
"path": "pages/mine/mine-edit"
},
{
"path": "pages/mine/mine-password"
}
],
"globalStyle": {
"navigationStyle": "custom",
@ -53,12 +70,12 @@
"selectedColor": "#587ff0",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [{
"list": [
{
"pagePath": "pages/chat/chat",
"iconPath": "static/tarbar/chat.png",
"selectedIconPath": "static/tarbar/chat_active.png",
"text": "消息"
},
{
"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>
<script>
import UNI_APP from '@/.env.js'
export default {
data() {
return {
url: "",
wv: '',
isHost: false,
groupId: null,
inviterId: null,
userInfos: []
}
import UNI_APP from '@/.env.js'
export default {
data() {
return {
url: "",
wv: '',
isHost: false,
groupId: null,
inviterId: null,
userInfos: []
}
},
methods: {
onMessage(e) {
this.onWebviewMessage(e.detail.data[0]);
},
methods: {
onMessage(e) {
this.onWebviewMessage(e.detail.data[0]);
},
onInsertMessage(msgInfo) {
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() {
console.log("onBackPress")
this.sendMessageToWebView("NAV_BACK", {})
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;
}
},
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);
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
//
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')
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() {
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>
<style lang="scss" scoped>
.chat-group-video {
.header {
display: flex;
justify-content: center;
align-items: center;
height: 60rpx;
padding: 5px;
background-color: white;
line-height: 50px;
font-size: 40rpx;
font-weight: 600;
.chat-group-video {
.header {
display: flex;
justify-content: center;
align-items: center;
height: 60rpx;
padding: 5px;
background-color: white;
line-height: 50px;
font-size: 40rpx;
font-weight: 600;
.btn-side {
position: absolute;
line-height: 60rpx;
font-size: 28rpx;
cursor: pointer;
.btn-side {
position: absolute;
line-height: 60rpx;
font-size: 28rpx;
cursor: pointer;
&.left {
left: 30rpx;
}
&.left {
left: 30rpx;
}
&.right {
right: 30rpx;
}
&.right {
right: 30rpx;
}
}
}
}
</style>

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

@ -3,101 +3,98 @@
</template>
<script>
import UNI_APP from '@/.env.js'
export default {
data() {
return {
url: "",
wv: '',
mode: "video",
isHost: true,
friend: {}
}
import UNI_APP from '@/.env.js'
export default {
data() {
return {
url: "",
wv: '',
mode: "video",
isHost: true,
friend: {}
}
},
methods: {
onMessage(e) {
this.onWebviewMessage(e.detail.data[0]);
},
methods: {
onMessage(e) {
this.onWebviewMessage(e.detail.data[0]);
},
onWebviewMessage(event) {
console.log("来自webview的消息:" + JSON.stringify(event))
switch (event.key) {
case "WV_READY":
this.initWebView();
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);
},
onWebviewMessage(event) {
console.log("来自webview的消息:" + JSON.stringify(event))
switch (event.key) {
case "WV_READY":
this.initWebView();
break;
case "WV_CLOSE":
uni.navigateBack();
break;
}
},
onBackPress() {
this.sendMessageToWebView("NAV_BACK",{})
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
},
onLoad(options) {
uni.$on('WS_RTC_PRIVATE', msg => {
// web-view
this.sendMessageToWebView("RTC_MESSAGE", msg);
})
initWebView() {
// #ifdef APP-PLUS
// APPwebview
this.wv = this.$scope.$getAppWebview().children()[0]
// #endif
// #ifdef H5
window.onmessage = (e) => {
this.onWebviewMessage(e.data.data.arg);
}
// H5webviewiframe
this.wv = document.getElementById('chat-video-wv').contentWindow
// #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')
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() {
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>
<style lang="scss" scoped>
.chat-web-view{
}
.chat-web-view {}
</style>

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

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

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

@ -9,17 +9,17 @@
<view class="info-item">
<view class="info-primary">
<text class="info-username">
{{userInfo.userName}}
{{ userInfo.userName }}
</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 == 0" class="iconfont icon-man" color="darkblue"></text>
<text v-show="userInfo.sex == 1" class="iconfont icon-girl" color="darkred"></text>
</view>
<view class="info-text">
<text class="label-text">
昵称:
</text>
<text class="content-text">
{{userInfo.nickName}}
{{ userInfo.nickName }}
</text>
</view>
<view class="info-text">
@ -28,7 +28,7 @@
签名:
</text>
<text class="content-text">
{{userInfo.signature}}
{{ userInfo.signature }}
</text>
</view>
</view>
@ -45,174 +45,174 @@
</template>
<script>
export default {
data() {
return {
userInfo: {}
export default {
data() {
return {
userInfo: {}
}
},
methods: {
onShowFullImage() {
let imageUrl = this.userInfo.headImage;
if (imageUrl) {
uni.previewImage({
urls: [imageUrl]
})
}
},
methods: {
onShowFullImage() {
let imageUrl = this.userInfo.headImage;
if (imageUrl) {
uni.previewImage({
urls: [imageUrl]
})
onSendMessage() {
let chat = {
type: 'PRIVATE',
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({
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
}
},
onSendMessage() {
let chat = {
type: 'PRIVATE',
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
this.friendStore.addFriend(friend);
uni.showToast({
title: '对方已成为您的好友',
icon: 'none'
})
},
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);
uni.showToast({
title: '对方已成为您的好友',
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'
})
})
},
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: {
isFriend() {
return !!this.friendInfo;
},
friendInfo() {
let friends = this.friendStore.friends;
let friend = friends.find((f) => f.id == this.userInfo.id);
return friend;
}
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);
})
},
onLoad(options) {
//
this.loadUserInfo(options.id);
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: {
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>
<style lang="scss" scoped>
.user-info {
.content {
height: 200rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
.user-info {
.content {
height: 200rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 20rpx;
.info-item {
display: flex;
align-items: flex-start;
flex-direction: column;
padding-left: 40rpx;
flex: 1;
.info-item {
display: flex;
align-items: flex-start;
flex-direction: column;
padding-left: 40rpx;
flex: 1;
.info-text {
line-height: 1.5;
//margin-bottom: 10rpx;
}
.info-text {
line-height: 1.5;
//margin-bottom: 10rpx;
}
.label-text {
font-size: $im-font-size-small;
color: $im-text-color-light;
.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;
}
.content-text {
font-size: $im-font-size-small;
color: $im-text-color-light;
}
.info-primary {
display: flex;
align-items: center;
margin-bottom: 10rpx;
.info-primary {
display: flex;
align-items: center;
margin-bottom: 10rpx;
.info-username {
font-size: $im-font-size-large;
font-weight: 600;
}
.info-username {
font-size: $im-font-size-large;
font-weight: 600;
}
.icon-man {
color: $im-text-color;
font-size: $im-font-size-large;
padding-left: 10rpx;
}
.icon-man {
color: $im-text-color;
font-size: $im-font-size-large;
padding-left: 10rpx;
}
.icon-girl {
color: darkred;
}
.icon-girl {
color: darkred;
}
}
}
}
}
</style>

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

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

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

@ -1,146 +1,150 @@
<template>
<nav-bar add search @add="onAddNewFriends" @search="showSearch = !showSearch">好友</nav-bar>
<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-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 class="friend-tip" v-if="friends.length==0">
<view class="friend-tip" v-if="friends.length == 0">
温馨提示您现在还没有任何好友快点击右上方'+'按钮添加好友吧~
</view>
<view class="friend-items" v-else>
<up-index-list :index-list="friendIdx" >
<template v-for="(friends,i) in friendGroups">
<up-index-list :index-list="friendIdx">
<template v-for="(friends, i) in friendGroups">
<up-index-item>
<up-index-anchor :text="friendIdx[i]=='*'?'在线':friendIdx[i]"></up-index-anchor>
<view v-for="(friend,idx) in friends" :key="idx">
<up-index-anchor :text="friendIdx[i] == '*' ? '在线' : friendIdx[i]"></up-index-anchor>
<view v-for="(friend, idx) in friends" :key="idx">
<friend-item :friend="friend"></friend-item>
</view>
</up-index-item>
</template>
</up-index-list>
</view>
</view>
</template>
<script>
import { pinyin } from 'pinyin-pro';
import { pinyin } from 'pinyin-pro';
export default {
data() {
return {
showSearch: false,
searchText: ''
}
export default {
data() {
return {
showSearch: false,
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: {
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);
}
friendIdx() {
return Array.from(this.friendGroupMap.keys());
},
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;
},
friendIdx(){
return Array.from(this.friendGroupMap.keys());
},
friendGroups(){
return Array.from(this.friendGroupMap.values());
}
friendGroups() {
return Array.from(this.friendGroupMap.values());
}
}
}
</script>
<style lang="scss" scoped>
.friend {
position: relative;
display: flex;
flex-direction: column;
.friend {
position: relative;
display: flex;
flex-direction: column;
:deep(.u-index-anchor){
height: 60rpx !important;
background-color: unset !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;
}
:deep(.u-index-anchor) {
height: 60rpx !important;
background-color: unset !important;
border-bottom: none !important;
}
.friend-tip {
position: absolute;
top: 400rpx;
padding: 50rpx;
text-align: center;
line-height: 50rpx;
color: $im-text-color-lighter;
}
: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 {
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 {
flex: 1;
padding: 0;
overflow: hidden;
position: relative;
.scroll-bar {
height: 100%;
}
.scroll-bar {
height: 100%;
}
}
}
</style>

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

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

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

@ -1,14 +1,14 @@
<template>
<view v-if="userStore.userInfo.type == 1" class="page group-info">
<nav-bar back>群聊信息</nav-bar>
<view v-if="!group.quit" class="group-members">
<nav-bar back>群聊信息</nav-bar>
<view v-if="!group.quit" class="group-members">
<view class="member-items">
<view v-for="(member,idx) in groupMembers" :key="idx">
<view class="member-item" v-if="idx<9">
<view v-for="(member, idx) in groupMembers" :key="idx">
<view class="member-item" v-if="idx < 9">
<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">
<text>{{member.showNickName}}</text>
<text>{{ member.showNickName }}</text>
</view>
</view>
</view>
@ -16,38 +16,37 @@
<uni-icons type="plusempty" size="20" color="#888888"></uni-icons>
</view>
</view>
<view class="member-more" @click="onShowMoreMmeber()">{{`查看全部群成员${groupMembers.length}`}}></view>
<view class="member-more" @click="onShowMoreMmeber()">{{ `查看全部群成员${groupMembers.length}` }}></view>
</view>
<view class="group-detail">
<uni-section title="群聊名称">
<template v-slot:right>
<text class="detail-text">{{group.name}}</text>
<text class="detail-text">{{ group.name }}</text>
</template>
</uni-section>
<uni-section title="群主">
<template v-slot:right>
<text class="detail-text">{{ownerName}}</text>
<text class="detail-text">{{ ownerName }}</text>
</template>
</uni-section>
<uni-section title="群名备注">
<template v-slot:right>
<text class="detail-text"> {{group.remarkGroupName}}</text>
<text class="detail-text"> {{ group.remarkGroupName }}</text>
</template>
</uni-section>
<uni-section title="我在本群的昵称">
<template v-slot:right>
<text class="detail-text"> {{group.showNickName}}</text>
<text class="detail-text"> {{ group.showNickName }}</text>
</template>
</uni-section>
<uni-section v-if="group.notice" title="群公告">
<uni-notice-bar :text="group.notice" />
</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>
<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="onDissolveGroup()"></btn-bar>
</bar-group>
@ -55,213 +54,213 @@
</template>
<script>
export default {
data() {
return {
groupId: null,
group:{},
groupMembers: []
}
export default {
data() {
return {
groupId: null,
group: {},
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: {
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)
}
})
});
}
});
},
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)
}
})
});
}
});
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() {
this.$http({
url: `/group/find/${this.groupId}`,
method: 'GET'
}).then((group) => {
this.group = group;
//
this.chatStore.updateChatFromGroup(group);
//
this.groupStore.updateGroup(group);
},
loadGroupInfo() {
this.$http({
url: `/group/find/${this.groupId}`,
method: 'GET'
}).then((group) => {
this.group = group;
//
this.chatStore.updateChatFromGroup(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: {
ownerName() {
let member = this.groupMembers.find((m) => m.userId == this.group.ownerId);
return member && member.showNickName;
},
isOwner() {
return this.group.ownerId == this.userStore.userInfo.id;
}
loadGroupMembers() {
console.log("loadGroupMembers")
this.$http({
url: `/group/members/${this.groupId}`,
method: "GET"
}).then((members) => {
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) {
this.groupId = options.id;
//
this.loadGroupInfo(options.id);
//
this.loadGroupMembers(options.id)
isOwner() {
return this.group.ownerId == this.userStore.userInfo.id;
}
},
onLoad(options) {
this.groupId = options.id;
//
this.loadGroupInfo(options.id);
//
this.loadGroupMembers(options.id)
}
}
</script>
<style lang="scss" scoped>
.group-info {
.group-members {
padding: 30rpx;
background: white;
.member-items {
.group-info {
.group-members {
padding: 30rpx;
background: white;
.member-items {
display: flex;
flex-wrap: wrap;
.member-item {
width: 120rpx;
display: flex;
flex-wrap: wrap;
flex-direction: column;
margin: 8rpx 2rpx;
position: relative;
align-items: center;
padding-right: 5px;
white-space: nowrap;
.member-item {
width: 120rpx;
display: flex;
flex-direction: column;
margin: 8rpx 2rpx;
position: relative;
align-items: center;
padding-right: 5px;
.member-name {
width: 100%;
flex: 1;
overflow: hidden;
text-align: center;
white-space: nowrap;
.member-name {
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%;
padding-top: 8rpx;
font-size: $im-font-size-smaller;
}
}
.member-more {
padding-top: 24rpx;
text-align: center;
font-size: $im-font-size-small;
color: $im-text-color-lighter;
.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 {
padding-top: 24rpx;
text-align: center;
font-size: $im-font-size-small;
color: $im-text-color-lighter;
}
}
.group-detail {
margin-top: 30rpx;
padding: 20rpx 20rpx;
background: white;
.detail-text{
font-size: $im-font-size;
}
.group-edit {
padding-top: 30rpx;
text-align: center;
font-size: $im-font-size-small;
color: $im-text-color-lighter;
}
.group-detail {
margin-top: 30rpx;
padding: 20rpx 20rpx;
background: white;
.detail-text {
font-size: $im-font-size;
}
.group-edit {
padding-top: 30rpx;
text-align: center;
font-size: $im-font-size-small;
color: $im-text-color-lighter;
}
}
}
</style>

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

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

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

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

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

@ -1,19 +1,19 @@
<template>
<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-search">
<uni-search-bar v-model="searchText" cancelButton="none" radius="100"
placeholder="点击搜索群聊"></uni-search-bar>
</view>
</view>
<view class="group-tip" v-if="groupStore.groups.length==0">
<view class="group-tip" v-if="groupStore.groups.length == 0">
温馨提示您现在还没有加入任何群聊点击右上方'+'按钮可以创建群聊哦~
</view>
<view class="group-items" v-else>
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
<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>
</view>
</scroll-view>
@ -22,52 +22,51 @@
</template>
<script>
export default {
data() {
return {
showSearch: false,
searchText: ""
}
},
methods: {
onFocusSearch() {
},
onCreateNewGroup() {
uni.navigateTo({
url: "/pages/group/group-edit"
})
}
export default {
data() {
return {
showSearch: false,
searchText: ""
}
},
methods: {
onFocusSearch() {
},
onCreateNewGroup() {
uni.navigateTo({
url: "/pages/group/group-edit"
})
}
}
}
</script>
<style lang="scss" scoped>
.group {
position: relative;
display: flex;
flex-direction: column;
.group {
position: relative;
display: flex;
flex-direction: column;
.group-tip {
position: absolute;
top: 400rpx;
padding: 50rpx;
text-align: left;
line-height: 50rpx;
color: darkblue;
font-size: 30rpx;
}
.group-tip {
position: absolute;
top: 400rpx;
padding: 50rpx;
text-align: left;
line-height: 50rpx;
color: darkblue;
font-size: 30rpx;
}
.group-items {
flex: 1;
padding: 0;
overflow: hidden;
position: relative;
.group-items {
flex: 1;
padding: 0;
overflow: hidden;
position: relative;
.scroll-bar {
height: 100%;
}
.scroll-bar {
height: 100%;
}
}
}
</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-forms-item>
<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>
<button class="btn-submit" @click="submit" type="primary">登录</button>
</uni-forms>
@ -17,85 +17,85 @@
</template>
<script>
export default {
data() {
return {
loginForm: {
terminal: 1, // APP
userName: '',
password: ''
export default {
data() {
return {
loginForm: {
terminal: 1, // APP
userName: '',
password: ''
},
rules: {
userName: {
rules: [{
required: true,
errorMessage: '请输入用户名',
}]
},
rules: {
userName: {
rules: [{
required: true,
errorMessage: '请输入用户名',
}]
},
password: {
rules: [{
required: true,
errorMessage: '请输入密码',
}]
}
password: {
rules: [{
required: true,
errorMessage: '请输入密码',
}]
}
}
},
methods: {
submit() {
this.$http({
url: '/login',
data: this.loginForm,
method: 'POST'
}).then(loginInfo => {
console.log("登录成功,自动跳转到聊天页面...")
uni.setStorageSync("userName", this.loginForm.userName);
uni.setStorageSync("password", this.loginForm.password);
uni.setStorageSync("loginInfo", loginInfo);
// App.vue
getApp().$vm.init()
//
uni.switchTab({
url: "/pages/chat/chat"
})
}
},
methods: {
submit() {
this.$http({
url: '/login',
data: this.loginForm,
method: 'POST'
}).then(loginInfo => {
console.log("登录成功,自动跳转到聊天页面...")
uni.setStorageSync("userName", this.loginForm.userName);
uni.setStorageSync("password", this.loginForm.password);
uni.setStorageSync("loginInfo", loginInfo);
// App.vue
getApp().$vm.init()
//
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>
<style lang="scss" scoped>
.login {
.title {
padding-top: 150rpx;
padding-bottom: 50rpx;
color: $im-color-primary;
text-align: center;
font-size: 24px;
font-weight: bold;
}
.login {
.title {
padding-top: 150rpx;
padding-bottom: 50rpx;
color: $im-color-primary;
text-align: center;
font-size: 24px;
font-weight: bold;
}
.form {
padding: 50rpx;
.form {
padding: 50rpx;
.btn-submit {
margin-top: 80rpx;
border-radius: 50rpx;
}
.btn-submit {
margin-top: 80rpx;
border-radius: 50rpx;
}
}
.nav-register {
position: fixed;
width: 100%;
bottom: 100rpx;
color: $im-color-primary;
text-align: center;
font-size: $im-font-size;
}
.nav-register {
position: fixed;
width: 100%;
bottom: 100rpx;
color: $im-color-primary;
text-align: center;
font-size: $im-font-size;
}
}
</style>

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

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

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

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

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

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

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

@ -16,128 +16,128 @@
</uni-forms-item>
<button class="btn-submit" @click="submit" type="primary">注册并登陆</button>
</uni-forms>
<navigator class="nav-login" url="/pages/login/login" >
<navigator class="nav-login" url="/pages/login/login">
返回登陆页面
</navigator>
</view>
</template>
<script>
export default {
data() {
return {
dataForm: {
userName: '',
nickName: '',
password: '',
corfirmPassword: ''
export default {
data() {
return {
dataForm: {
userName: '',
nickName: '',
password: '',
corfirmPassword: ''
},
rules: {
userName: {
rules: [{
required: true,
errorMessage: '请输入用户名',
}]
},
nickName: {
rules: [{
required: true,
errorMessage: '请输入昵称',
}]
},
rules: {
userName: {
rules: [{
required: true,
errorMessage: '请输入用户名',
}]
},
nickName: {
rules: [{
required: true,
errorMessage: '请输入昵称',
}]
},
password: {
rules: [{
required: true,
errorMessage: '请输入密码',
}]
},
corfirmPassword: {
rules: [{
required: true,
errorMessage: '请输入确认密码',
}, {
validateFunction: (rule, value, data, callback) => {
console.log("validateFunction")
if (data.password != value) {
callback('两次密码输入不一致')
}
return true;
password: {
rules: [{
required: true,
errorMessage: '请输入密码',
}]
},
corfirmPassword: {
rules: [{
required: true,
errorMessage: '请输入确认密码',
}, {
validateFunction: (rule, value, data, callback) => {
console.log("validateFunction")
if (data.password != value) {
callback('两次密码输入不一致')
}
}]
}
return true;
}
}]
}
}
},
methods: {
submit() {
this.$refs.form.validate().then(() => {
this.$http({
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
}
}
},
methods: {
submit() {
this.$refs.form.validate().then(() => {
this.$http({
url: '/login',
data: loginForm,
url: '/register',
data: this.dataForm,
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"
}).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({
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>
<style lang="scss" scoped>
.register {
.title {
padding-top: 150rpx;
padding-bottom: 50rpx;
color: $im-color-primary;
text-align: center;
font-size: 24px;
font-weight: 600;
}
.register {
.title {
padding-top: 150rpx;
padding-bottom: 50rpx;
color: $im-color-primary;
text-align: center;
font-size: 24px;
font-weight: 600;
}
.form {
padding: 50rpx;
.form {
padding: 50rpx;
.btn-submit {
margin-top: 80rpx;
border-radius: 50rpx;
}
.btn-submit {
margin-top: 80rpx;
border-radius: 50rpx;
}
}
.nav-login {
position: fixed;
width: 100%;
bottom: 100rpx;
color: $im-color-primary;
text-align: center;
font-size: 32rpx;
}
.nav-login {
position: fixed;
width: 100%;
bottom: 100rpx;
color: $im-color-primary;
text-align: center;
font-size: 32rpx;
}
}
</style>

32
im-uniapp/store/chatStore.js

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

2
im-uniapp/store/userStore.js

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

2
im-uniapp/vite.config.js

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

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

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

Loading…
Cancel
Save