Browse Source

!123 发布3.1.0版本

Merge pull request !123 from blue/v_3.0.0
master
blue 1 year ago
committed by Gitee
parent
commit
b7d202795f
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 1
      .gitignore
  2. 6
      README.md
  3. 1
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java
  4. 2
      im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java
  5. 32
      im-uniapp/App.vue
  6. 40
      im-uniapp/components/bar/arrow-bar.vue
  7. 17
      im-uniapp/components/bar/bar-group.vue
  8. 68
      im-uniapp/components/bar/btn-bar.vue
  9. 61
      im-uniapp/components/bar/switch-bar.vue
  10. 30
      im-uniapp/components/chat-at-box/chat-at-box.vue
  11. 7
      im-uniapp/components/chat-group-readed/chat-group-readed.vue
  12. 52
      im-uniapp/components/chat-item/chat-item.vue
  13. 98
      im-uniapp/components/chat-message-item/chat-message-item.vue
  14. 32
      im-uniapp/components/chat-record/chat-record.vue
  15. 3
      im-uniapp/components/file-upload/file-upload.vue
  16. 24
      im-uniapp/components/friend-item/friend-item.vue
  17. 20
      im-uniapp/components/group-item/group-item.vue
  18. 4
      im-uniapp/components/group-member-selector/group-member-selector.vue
  19. 5
      im-uniapp/components/group-rtc-join/group-rtc-join.vue
  20. 41
      im-uniapp/components/head-image/head-image.vue
  21. 3
      im-uniapp/components/image-upload/image-upload.vue
  22. 4
      im-uniapp/components/loading/loading.vue
  23. 121
      im-uniapp/components/long-press-menu/long-press-menu.vue
  24. 119
      im-uniapp/components/nav-bar/nav-bar.vue
  25. 121
      im-uniapp/components/pop-menu/pop-menu.vue
  26. 60
      im-uniapp/im-var.scss
  27. 141
      im-uniapp/im.scss
  28. 11
      im-uniapp/main.js
  29. 11
      im-uniapp/manifest.json
  30. 61
      im-uniapp/pages.json
  31. 72
      im-uniapp/pages/chat/chat-box.vue
  32. 1
      im-uniapp/pages/chat/chat-group-video.vue
  33. 5
      im-uniapp/pages/chat/chat-private-video.vue
  34. 38
      im-uniapp/pages/chat/chat.vue
  35. 80
      im-uniapp/pages/common/user-info.vue
  36. 22
      im-uniapp/pages/friend/friend-add.vue
  37. 55
      im-uniapp/pages/friend/friend.vue
  38. 29
      im-uniapp/pages/group/group-edit.vue
  39. 68
      im-uniapp/pages/group/group-info.vue
  40. 33
      im-uniapp/pages/group/group-invite.vue
  41. 28
      im-uniapp/pages/group/group-member.vue
  42. 25
      im-uniapp/pages/group/group.vue
  43. 12
      im-uniapp/pages/login/login.vue
  44. 30
      im-uniapp/pages/mine/mine-edit.vue
  45. 19
      im-uniapp/pages/mine/mine-password.vue
  46. 88
      im-uniapp/pages/mine/mine.vue
  47. 10
      im-uniapp/pages/register/register.vue
  48. BIN
      im-uniapp/static/logo/logo.png
  49. BIN
      im-uniapp/static/tarbar/mine.png
  50. BIN
      im-uniapp/static/tarbar/mine_active.png
  51. 2
      im-uniapp/store/userStore.js
  52. 46
      im-uniapp/uni.scss
  53. BIN
      im-uniapp/unpackage/res/icons/1024x1024.png
  54. BIN
      im-uniapp/unpackage/res/icons/120x120.png
  55. BIN
      im-uniapp/unpackage/res/icons/144x144.png
  56. BIN
      im-uniapp/unpackage/res/icons/152x152.png
  57. BIN
      im-uniapp/unpackage/res/icons/167x167.png
  58. BIN
      im-uniapp/unpackage/res/icons/180x180.png
  59. BIN
      im-uniapp/unpackage/res/icons/192x192.png
  60. BIN
      im-uniapp/unpackage/res/icons/20x20.png
  61. BIN
      im-uniapp/unpackage/res/icons/29x29.png
  62. BIN
      im-uniapp/unpackage/res/icons/40x40.png
  63. BIN
      im-uniapp/unpackage/res/icons/58x58.png
  64. BIN
      im-uniapp/unpackage/res/icons/60x60.png
  65. BIN
      im-uniapp/unpackage/res/icons/72x72.png
  66. BIN
      im-uniapp/unpackage/res/icons/76x76.png
  67. BIN
      im-uniapp/unpackage/res/icons/80x80.png
  68. BIN
      im-uniapp/unpackage/res/icons/87x87.png
  69. BIN
      im-uniapp/unpackage/res/icons/96x96.png
  70. 2
      im-uniapp/vite.config.js
  71. 24
      im-web/package.json
  72. BIN
      im-web/public/logo.png
  73. 95
      im-web/src/App.vue
  74. 5
      im-web/src/api/camera.js
  75. 2
      im-web/src/api/emotion.js
  76. BIN
      im-web/src/assets/image/online_app.png
  77. BIN
      im-web/src/assets/image/online_web.png
  78. 112
      im-web/src/assets/style/element.scss
  79. 43
      im-web/src/assets/style/global.css
  80. 91
      im-web/src/assets/style/im.scss
  81. 6
      im-web/src/assets/style/thems.scss
  82. 8
      im-web/src/components/chat/ChatAtBox.vue
  83. 66
      im-web/src/components/chat/ChatBox.vue
  84. 13
      im-web/src/components/chat/ChatGroupMember.vue
  85. 4
      im-web/src/components/chat/ChatGroupReaded.vue
  86. 69
      im-web/src/components/chat/ChatGroupSide.vue
  87. 9
      im-web/src/components/chat/ChatHistory.vue
  88. 9
      im-web/src/components/chat/ChatInput.vue
  89. 48
      im-web/src/components/chat/ChatItem.vue
  90. 78
      im-web/src/components/chat/ChatMessageItem.vue
  91. 1
      im-web/src/components/chat/ChatRecord.vue
  92. 43
      im-web/src/components/common/Emotion.vue
  93. 7
      im-web/src/components/common/FileUpload.vue
  94. 14
      im-web/src/components/common/FullImage.vue
  95. 38
      im-web/src/components/common/HeadImage.vue
  96. 22
      im-web/src/components/common/RightMenu.vue
  97. 22
      im-web/src/components/common/UserInfo.vue
  98. 30
      im-web/src/components/friend/AddFriend.vue
  99. 39
      im-web/src/components/friend/FriendItem.vue
  100. 38
      im-web/src/components/group/AddGroupMember.vue

1
.gitignore

@ -12,4 +12,3 @@
/im-web/dist/
/im-uniapp/node_modules/
/im-uniapp/package-lock.json
/im-uniapp/unpackage/

6
README.md

@ -122,9 +122,9 @@ https://www.yuque.com/u1475064/mufu2a/vn5u10ephxh9sau8
![输入图片说明](%E6%88%AA%E5%9B%BE/app/2.jpg)
#### 加入交流群
1群目前已满员,扫码进入2群:
![输入图片说明](%E6%88%AA%E5%9B%BE/%E4%BA%A4%E6%B5%81%E7%BE%A42.png)
群1: 741174521(已满)
群2: 937470451(已满)
群3: 1012017031
欢迎进群与小伙们一起交流, **申请加群前请务必先star哦**

1
im-platform/src/main/java/com/bx/implatform/service/impl/GroupServiceImpl.java

@ -65,6 +65,7 @@ public class GroupServiceImpl extends ServiceImpl<GroupMapper, Group> implements
member.setGroupId(group.getId());
member.setUserId(user.getId());
member.setHeadImage(user.getHeadImageThumb());
member.setUserNickName(user.getNickName());
member.setRemarkNickName(vo.getRemarkNickName());
member.setRemarkGroupName(vo.getRemarkGroupName());
groupMemberService.save(member);

2
im-platform/src/main/java/com/bx/implatform/service/impl/WebrtcPrivateServiceImpl.java

@ -314,7 +314,7 @@ public class WebrtcPrivateServiceImpl implements WebrtcPrivateService {
sendMessage.setSender(new IMUserInfo(rtcSession.getCallerId(), rtcSession.getCallerTerminal()));
sendMessage.setRecvId(rtcSession.getCallerId());
sendMessage.setSendToSelf(false);
sendMessage.setSendResult(false);
sendMessage.setSendResult(true);
sendMessage.setData(messageInfo);
imClient.sendPrivateMessage(sendMessage);
// 推给接听方

32
im-uniapp/App.vue

@ -383,26 +383,48 @@
<style lang="scss">
@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
.tab-page {
position: relative;
display: flex;
flex-direction: column;
// #ifdef H5
height: calc(100vh - 50px); // h5100vh
height: calc(100vh - 50px - $im-nav-bar-height); // h5100vh
top: $im-nav-bar-height;
// #endif
// #ifndef H5
height: calc(100vh);
height: calc(100vh - var(--status-bar-height) - $im-nav-bar-height); // app
top: calc($im-nav-bar-height + var(--status-bar-height));
// #endif
background-color: #f8f8f8;
color: $im-text-color;
background-color: $im-bg;
font-size: $im-font-size;
font-family: $font-family;
}
.page {
height: calc(100vh);
background-color: #f8f8f8;
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>

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

@ -0,0 +1,40 @@
<template>
<view class="arrow-bar">
<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
}
},
}
</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;
}
}
</style>

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

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

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

@ -0,0 +1,68 @@
<template>
<view class="btn-bar" :style="style">
<text v-if="icon" class="icon iconfont" :class="icon"></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"
}
},
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;
}
}
</style>

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

@ -0,0 +1,61 @@
<template>
<view class="switch-bar">
<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
}
},
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;
.title {
flex: 1;
margin-left: 40rpx;
}
.switch {
margin-right: 40rpx;
}
}
</style>

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

@ -9,8 +9,8 @@
</view>
<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">
<head-image :name="m.showNickName" :url="m.headImage" :size="60"></head-image>
<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>
</scroll-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" @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="90"></head-image>
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-checked">-->
<!-- <radio :checked="m.checked" @click.stop="onSwitchChecked(m)" />-->
<!-- </view>-->
</view>
</view>
</scroll-view>
@ -108,12 +107,11 @@
<style lang="scss" scoped>
.chat-at-box {
position: relative;
border: #dddddd solid 1rpx;
display: flex;
flex-direction: column;
background-color: white;
padding: 10rpx;
border-radius: 15rpx;
//border-radius: 15rpx;
.chat-at-top {
display: flex;
@ -149,19 +147,23 @@
overflow: hidden;
.member-item {
height: 120rpx;
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: 30rpx;
font-weight: 600;
font-size: $im-font-size;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;

7
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" active-color="#587ff0" @clickItem="onClickItem"/>
<uni-segmented-control :current="current" :values="items" style-type="button"
@clickItem="onClickItem" />
</view>
<view class="content">
<view v-if="current === 0">
@ -96,12 +97,12 @@
<style lang="scss" scoped>
.chat-group-readed {
position: relative;
border: #dddddd solid 1rpx;
display: flex;
flex-direction: column;
background-color: white;
padding: 10rpx;
border-radius: 15rpx;
//border-radius: 15rpx;
.scroll-bar {
height: 800rpx;
}

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

@ -3,13 +3,13 @@
<!--rich-text中的表情包会屏蔽事件所以这里用一个遮罩层捕获点击事件 -->
<view class="mask" @tap="showChatBox()"></view>
<view class="left">
<head-image :url="chat.headImage" :name="chat.showName" :size="90"></head-image>
<head-image :url="chat.headImage" :name="chat.showName"></head-image>
</view>
<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"></uni-tag>
<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>
@ -17,7 +17,7 @@
<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" size="small" :max-num="99" :text="chat.unreadCount" />
<uni-badge v-if="chat.unreadCount > 0" :max-num="99" :text="chat.unreadCount" />
</view>
</view>
</view>
@ -76,28 +76,29 @@
<style scoped lang="scss">
.chat-item {
height: 100rpx;
height: 96rpx;
display: flex;
margin-bottom: 2rpx;
position: relative;
padding: 10rpx 20rpx;
padding: 18rpx 20rpx;
align-items: center;
background-color: white;
white-space: nowrap;
&:hover {
background-color: #f5f6ff;
background-color: $im-bg-active;
}
&.active {
background-color: #f5f6ff;
background-color: $im-bg-active;
}
.mask {
position: absolute;
width: 100%;
height: 100%;
left: 0;
right: 0;
z-index: 99;
}
@ -108,27 +109,24 @@
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-name {
display: flex;
line-height: 44rpx;
height: 44rpx;
.chat-name-text {
flex: 1;
font-size: 30rpx;
font-weight: 600;
font-size: $im-font-size-large;
white-space: nowrap;
overflow: hidden;
display: flex;
@ -138,19 +136,15 @@
text-align: center;
margin-left: 5rpx;
border: 0;
height: 30rpx;
line-height: 30rpx;
font-size: 20rpx;
padding: 1px 5px;
background-color: #de1c1c;
opacity: 0.8;
//opacity: 0.8;
}
}
.chat-time {
font-size: 26rpx;
font-size: $im-font-size-smaller-extra;
color: $im-text-color-lighter;
text-align: right;
color: #888888;
white-space: nowrap;
overflow: hidden;
}
@ -158,24 +152,28 @@
.chat-content {
display: flex;
line-height: 60rpx;
height: 60rpx;
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
padding-top: 8rpx;
.chat-at-text {
color: #c70b0b;
font-size: 24rpx;
color: $im-color-danger;
}
.chat-send-name {
font-size: 26rpx;
font-size: $im-font-size-smaller;
}
.chat-content-text {
flex: 1;
font-size: 28rpx;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
img {
width: 40rpx !important;
height: 40rpx !important;
}
}
}

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

@ -9,31 +9,31 @@
</view>
<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="80"></head-image>
: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>
</view>
<view class="chat-msg-bottom">
<view v-if="msgInfo.type == $enums.MESSAGE_TYPE.TEXT">
<pop-menu :items="menuItems" @select="onSelectMenu">
<long-press-menu :items="menuItems" @select="onSelectMenu">
<rich-text class="chat-msg-text" :nodes="$emo.transform(msgInfo.content)"></rich-text>
</pop-menu>
</long-press-menu>
</view>
<view class="chat-msg-image" v-if="msgInfo.type == $enums.MESSAGE_TYPE.IMAGE">
<pop-menu :items="menuItems" @select="onSelectMenu">
<long-press-menu :items="menuItems" @select="onSelectMenu">
<view class="img-load-box">
<image class="send-image" mode="heightFix" :src="JSON.parse(msgInfo.content).thumbUrl"
<image class="send-image" mode="widthFix" :src="JSON.parse(msgInfo.content).thumbUrl"
lazy-load="true" @click.stop="onShowFullImage()">
</image>
<loading v-if="loading"></loading>
</view>
</pop-menu>
</long-press-menu>
<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">
<pop-menu :items="menuItems" @select="onSelectMenu">
<long-press-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"
@ -43,19 +43,19 @@
<view class="chat-file-icon iconfont icon-file"></view>
<loading v-if="loading"></loading>
</view>
</pop-menu>
</long-press-menu>
<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">
<long-press-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>
</view>
</pop-menu>
<pop-menu v-if="isAction" :items="menuItems" @select="onSelectMenu">
</long-press-menu>
<long-press-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"
class="iconfont icon-chat-voice"></text>
@ -63,7 +63,7 @@
class="iconfont icon-chat-video"></text>
<text>{{ msgInfo.content }}</text>
</view>
</pop-menu>
</long-press-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>
@ -230,15 +230,14 @@
.chat-msg-tip {
line-height: 60rpx;
text-align: center;
color: #555;
font-size: 24rpx;
color: $im-text-color-lighter;
font-size: $im-font-size-smaller-extra;
padding: 10rpx;
}
.chat-msg-normal {
position: relative;
font-size: 0;
margin-bottom: 15rpx;
margin-bottom: 22rpx;
padding-left: 110rpx;
min-height: 80rpx;
@ -254,10 +253,9 @@
.chat-msg-top {
display: flex;
flex-wrap: nowrap;
color: #333;
font-size: 24rpx;
line-height: 24rpx;
color: $im-text-color-lighter;
font-size: $im-font-size-smaller;
line-height: $im-font-size-smaller;
}
.chat-msg-bottom {
@ -266,13 +264,13 @@
.chat-msg-text {
position: relative;
line-height: 60rpx;
line-height: 1.6;
margin-top: 10rpx;
padding: 8rpx 20rpx;
background-color: #eee;
padding: 16rpx 24rpx;
background-color: $im-bg;
border-radius: 20rpx;
color: #333;
font-size: 30rpx;
color: $im-text-color;
font-size: $im-font-size;
text-align: left;
display: block;
word-break: break-all;
@ -287,9 +285,10 @@
width: 6rpx;
height: 6rpx;
border-style: solid dashed dashed;
border-color: #eee transparent transparent;
border-color: $im-bg transparent transparent;
overflow: hidden;
border-width: 18rpx;
//box-shadow: $im-box-shadow-dark;
}
}
@ -305,18 +304,16 @@
.send-image {
min-width: 200rpx;
min-height: 200rpx;
max-width: 400rpx;
max-height: 400rpx;
border: 8rpx solid #ebebf5;
max-width: 420rpx;
cursor: pointer;
border-radius: 4px;
}
}
.send-fail {
color: #e60c0c;
font-size: 30px;
color: $im-color-danger;
font-size: $im-font-size;
cursor: pointer;
margin: 0 20px;
}
@ -334,12 +331,10 @@
display: flex;
flex-wrap: nowrap;
align-items: center;
min-height: 80px;
border: #dddddd solid 1px;
border-radius: 10rpx;
background-color: #eeeeee;
min-height: 60px;
border-radius: 4px;
padding: 10px 15px;
box-shadow: 2px 2px 2px #c0c0c0;
box-shadow: $im-box-shadow-dark;
.chat-file-info {
flex: 1;
@ -349,7 +344,6 @@
width: 300rpx;
.chat-file-name {
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
word-break: break-all;
@ -380,7 +374,7 @@
}
.icon-voice-play {
font-size: 20px;
font-size: 18px;
padding-right: 8px;
}
}
@ -396,29 +390,29 @@
}
.chat-msg-status {
display: block;
line-height: $im-font-size-smaller-extra;
font-size: $im-font-size-smaller-extra;
padding-top: 2rpx;
.chat-readed {
font-size: 12px;
color: #888;
font-weight: 600;
display: block;
padding-top: 2rpx;
color: $im-text-color-lighter;
}
.chat-unread {
font-size: 12px;
color: #f23c0f;
font-weight: 600;
color: $im-color-danger;
}
}
.chat-receipt {
font-size: 13px;
color: darkblue;
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
font-weight: 600;
.icon-ok {
font-size: 20px;
color: #329432;
color: $im-color-success;
}
}
}
@ -444,13 +438,13 @@
.chat-msg-text {
margin-left: 10px;
background-color: #587ff0;
background-color: $im-color-primary-light-2;
color: #fff;
&:after {
left: auto;
right: -10px;
border-top-color: #587ff0;
right: -9px;
border-top-color: $im-color-primary-light-2;
}
}

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

@ -1,6 +1,6 @@
<template>
<view class="chat-record">
<view class="chat-record-bar" id="chat-record-bar" :style="recordBarStyle" @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>
<view v-if="recording" class="chat-record-window" :style="recordWindowStyle">
@ -130,12 +130,6 @@
const bottom = windowHeight - this.recordBarTop + 12;
return `bottom:${bottom}px;`
},
recordBarStyle() {
const bgColor = this.recording ? "royalblue" : "white";
const textColor = this.recording ? "white" : "black";
return `background-color:${bgColor};
color:${textColor};`
},
recordTip() {
if (this.druation > 50) {
return `${60 - this.druation}s后将停止录音`;
@ -157,7 +151,7 @@
height: 80rpx;
.note {
background: linear-gradient(to top, #395ff3 0%, #89aff3 100%);
background: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
width: 4px;
height: 50%;
border-radius: 5rpx;
@ -167,19 +161,19 @@
@keyframes loading {
0% {
background-image: linear-gradient(to right, #395ff3 0%, #89aff3 100%);
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, #395ff3 0%, #a9cff3 100%);
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, #395ff3 0%, #a9cff3 100%);
background-image: linear-gradient(to top, $im-color-primary-light-1 0%, $im-color-primary-light-6 100%);
height: 20%;
border-radius: 5rpx;
}
@ -192,13 +186,19 @@
margin: 10rpx;
border-radius: 10rpx;
text-align: center;
box-shadow: $im-box-shadow;
&.recording {
background-color: $im-color-primary;
color: #fff;
}
}
.chat-record-window {
position: fixed;
left: 0;
right: 0;
height: 360rpx;
width: 100%;
background-color: rgba(255, 255, 255, 0.95);
padding: 30rpx;
@ -211,7 +211,8 @@
.rc-tip {
text-align: center;
font-size: 30rpx;
font-size: $im-font-size-small;
color: $im-text-color-light;
margin-top: 20rpx;
}
@ -229,12 +230,9 @@
}
.red {
color: red !important;
color: $im-color-danger !important;
}
.black {
color: gray;
}
}
}
</style>

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

@ -113,5 +113,4 @@
}
</script>
<style>
</style>
<style></style>

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

@ -1,14 +1,11 @@
<template>
<view class="friend-item" @click="showFriendInfo()">
<head-image :name="friend.nickName" :online="friend.online" :url="friend.headImage"
:size="90"></head-image>
<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-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>
@ -37,7 +34,7 @@
<style scope lang="scss">
.friend-item {
height: 100rpx;
height: 90rpx;
display: flex;
margin-bottom: 1rpx;
position: relative;
@ -48,7 +45,7 @@
white-space: nowrap;
&:hover {
background-color: #f5f6ff;
background-color: $im-bg;
}
.friend-info {
@ -59,19 +56,18 @@
text-align: left;
.friend-name {
font-size: 30rpx;
font-weight: 600;
line-height: 60rpx;
font-size: $im-font-size;
white-space: nowrap;
overflow: hidden;
}
.friend-online {
margin-top: 4rpx;
.online {
padding-right: 4rpx;
width: 32rpx;
height: 32rpx;
width: 24rpx;
height: 24rpx;
}
}
}

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

@ -1,7 +1,6 @@
<template>
<view class="group-item" @click="showGroupInfo()">
<head-image :name="group.showGroupName"
:url="group.headImage" :size="90"></head-image>
<head-image :name="group.showGroupName" :url="group.headImage" size="small"></head-image>
<view class="group-name">
<view>{{ group.showGroupName }}</view>
</view>
@ -31,23 +30,26 @@
<style scope lang="scss">
.group-item {
height: 100rpx;
height: 90rpx;
display: flex;
margin-bottom: 1rpx;
margin-bottom: 2rpx;
position: relative;
padding: 10rpx;
padding-left: 20rpx;
padding: 18rpx 20rpx;
align-items: center;
background-color: white;
white-space: nowrap;
&:hover {
background-color: #f5f6ff;
background-color: $im-bg-active;
}
&.active {
background-color: $im-bg-active;
}
.group-name {
font-size: 32rpx;
font-size: $im-font-size;
padding-left: 20rpx;
font-weight: 600;
text-align: left;
white-space: nowrap;
overflow: hidden;

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

@ -9,7 +9,7 @@
</view>
<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>
@ -109,7 +109,6 @@
<style lang="scss" scoped>
.chat-group-member-choose {
position: relative;
border: #dddddd solid 1rpx;
display: flex;
flex-direction: column;
background-color: white;
@ -136,6 +135,7 @@
align-items: center;
height: 90rpx;
padding: 0 30rpx;
.user-item {
padding: 3rpx;
}

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

@ -1,7 +1,6 @@
<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>
@ -11,7 +10,7 @@
<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>

41
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>
@ -24,20 +24,20 @@
type: Number
},
size: {
type: Number,
default: 20
type: [Number, String],
default: 'default'
},
url: {
type: String
},
name: {
type: String,
default: "?"
default: null
},
online: {
type: Boolean,
default: false
}
},
},
methods: {
showUserInfo(e) {
@ -49,15 +49,30 @@
}
},
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;
height:${this.size}rpx;`
return `width:${this._size}rpx;
height:${this._size}rpx;`
},
avatarTextStyle() {
return `width: ${this.size}rpx;
height:${this.size}rpx;
background-color:${this.textColor};
font-size:${this.size*0.5}rpx;
return `width: ${this._size}rpx;
height:${this._size}rpx;
background-color:${this.name ? this.textColor : '#fff'};
font-size:${this._size * 0.5}rpx;
`
},
textColor() {

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

@ -89,5 +89,4 @@
}
</script>
<style>
</style>
<style></style>

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

@ -6,9 +6,7 @@
</template>
<script>
import {
computed
} from "vue"
export default {
data() {
return {}

121
im-uniapp/components/long-press-menu/long-press-menu.vue

@ -0,0 +1,121 @@
<template>
<view>
<view @longpress.stop="onLongPress($event)" @touchmove="onTouchMove" @touchend="onTouchEnd">
<slot></slot>
</view>
<view v-if="isShowMenu" class="menu-mask" @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)">
<text :style="itemStyle(item)"> {{ item.name }}</text>
</view>
</view>
</view>
</template>
<script>
export default {
name: "long-press-menu",
data() {
return {
isShowMenu: false,
isTouchMove: false,
style: ""
}
},
props: {
items: {
type: Array
}
},
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;
});
}
})
},
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:#000;`;
}
}
}
</script>
<style lang="scss" scoped>
.menu-mask {
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;
&:hover {
background: $im-bg-active;
}
.menu-icon {
margin-right: 10rpx;
}
}
}
</style>

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

@ -0,0 +1,119 @@
<template>
<view class="im-nav-bar">
<!-- #ifndef H5 -->
<view style="height: var(--status-bar-height)"></view>
<!-- #endif -->
<view class="im-nav-bar-content">
<view class="back" @click="handleBackClick" v-if="back">
<uni-icons type="back" :size="iconFontSize"></uni-icons>
</view>
<view class="title" v-if="title">
<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="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>
</view>
</view>
</view>
</template>
<script>
export default {
name: "nav-bar",
props: {
back: {
type: Boolean,
default: false
},
title: {
type: Boolean,
default: true
},
search: {
type: Boolean,
default: false
},
add: {
type: Boolean,
default: false
},
more: {
type: Boolean,
default: false
},
iconFontSize: {
type: Number,
default: 24
}
},
data() {
return {}
},
computed: {
height() {
}
},
methods: {
handleBackClick() {
uni.navigateBack({
delta: 1
})
}
}
}
</script>
<style scoped lang="scss">
.im-nav-bar {
background-color: #fff;
//background-color: $im-bg;
position: fixed;
top: 0;
width: 100%;
color: $im-text-color;
border-bottom: 1px solid $im-border-light;
font-size: $im-font-size-large;
z-index: 99;
.im-nav-bar-content {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
height: $im-nav-bar-height;
.title {}
.back {
position: absolute;
left: 0;
height: 100%;
display: flex;
align-items: center;
padding: 12px;
font-size: 22px;
box-sizing: border-box;
}
.btn {
position: absolute;
right: 0;
height: 100%;
display: flex;
padding: 12px;
align-items: center;
box-sizing: border-box;
.btn-item {
margin-left: 8px;
}
}
}
}
</style>

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

@ -1,121 +0,0 @@
<template>
<view>
<view @longpress.stop="onLongPress($event)" @touchmove="onTouchMove" @touchend="onTouchEnd">
<slot></slot>
</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="22"></uni-icons>
<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
}
},
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;
});
}
})
},
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%;
background-color: #333;
z-index: 999;
opacity: 0.5;
}
.menu {
position: fixed;
border: 1px solid #b4b4b4;
border-radius: 7px;
overflow: hidden;
background-color: #f5f6ff;
z-index: 1000;
.menu-item {
height: 25px;
min-width: 150rpx;
line-height: 25px;
font-size: 18px;
display: flex;
padding: 10px;
justify-content: center;
border-bottom: 1px solid #d0d0d8;
.menu-icon {
margin-right: 10rpx;
}
}
}
</style>

60
im-uniapp/im-var.scss

@ -0,0 +1,60 @@
// 颜色
$im-color-primary: #3e45d7;
$im-color-primary-light-1: mix(#fff, $im-color-primary, 10%);
$im-color-primary-light-2: mix(#fff, $im-color-primary, 20%);
$im-color-primary-light-3: mix(#fff, $im-color-primary, 30%);
$im-color-primary-light-4: mix(#fff, $im-color-primary, 40%);
$im-color-primary-light-5: mix(#fff, $im-color-primary, 50%);
$im-color-primary-light-6: mix(#fff, $im-color-primary, 60%);
$im-color-primary-light-7: mix(#fff, $im-color-primary, 70%);
$im-color-primary-light-8: mix(#fff, $im-color-primary, 80%);
$im-color-primary-light-9: mix(#fff, $im-color-primary, 90%);
$im-color-primary-dark-1: mix(#000, $im-color-primary, 10%);
$im-color-primary-dark-2: mix(#000, $im-color-primary, 20%);
$im-color-primary-dark-3: mix(#000, $im-color-primary, 30%);
$im-color-primary-dark-4: mix(#000, $im-color-primary, 40%);
$im-color-success: #18bc37;
$im-color-warning: #f3a73f;
$im-color-danger: #e43d33;
$im-color-info: #8f939c;
// 文字颜色
$im-text-color: #000000;
$im-text-color-light: #6a6a6a;
$im-text-color-lighter: #909399;
$im-text-color-lighter-extra: #c7c7c7;
// 边框颜色
$im-border: #F0F0F0;
$im-border-light: #EDEDED;
$im-border-lighter: #DCDCDC;
$im-border-lighter-extra: #B9B9B9;
// 文字大小
$im-font-size: 30rpx;
$im-font-size-small: 28rpx;
$im-font-size-smaller: 26rpx;
$im-font-size-smaller-extra: 24rpx;
$im-font-size-large: 32rpx;
$im-font-size-larger: 34rpx;
$im-font-size-larger-extra: 36rpx;
// 阴影
$im-box-shadow: 0 2px 4px rgba(0, 0, 0, .12), 0 0 6px rgba(0, 0, 0, .04);
$im-box-shadow-light: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
$im-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, .12);
$im-box-shadow-dark: 0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5);
// 背景
$im-bg: #f7f7f7;
$im-bg-active: #f1f1f1;
// 标题
$im-title-size: 26px;
$im-title-size-1: 22px;
$im-title-size-2: 18px;
$font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;
$im-nav-bar-height: 50px;

141
im-uniapp/im.scss

@ -0,0 +1,141 @@
/** 原生button样式 **/
uni-button {
font-size: $im-font-size !important;
}
uni-button[type='primary'] {
color: #fff !important;
background-color: $im-color-primary !important;
}
uni-button[type='primary'][plain] {
color: $im-color-primary !important;
border: 1px solid $im-color-primary;
background-color: transparent;
}
uni-button[type='warn'] {
color: #fff !important;
background-color: $im-color-danger !important;
}
uni-button[type='warn'][plain] {
color: $im-color-danger !important;
border: 1px solid $im-color-danger !important;
background-color: transparent !important;
}
uni-button[size='mini'] {
font-size: $im-font-size-smaller !important;
}
.button-hover[type='primary'] {
color: #fff !important;
background-color: $im-color-primary-dark-1 !important;
}
/** uni-ui input激活后边框、图标颜色 **/
.uni-easyinput__content.is-focused:not(.is-input-error-border) {
border-color: $im-color-primary-light-2 !important;
.content-clear-icon {
color: $im-color-primary-light-2 !important;
}
}
/** 底部导航 **/
.uni-tabbar-bottom .uni-tabbar {
box-shadow: $im-box-shadow;
}
.uni-tabbar-border {
display: none;
}
.segmented-control {
border-color: $im-color-primary !important;
.segmented-control__item--button {
border-color: $im-color-primary !important;
}
.segmented-control__item--button--active {
background-color: $im-color-primary !important;
.segmented-control__text{
color: #fff !important;
}
}
.segmented-control__text{
color: $im-color-primary !important;
}
}
.uni-radio-input {
//border-color: $im-color-primary !important;
//background-color: $im-color-primary !important;
}
.uni-section__content-title {
font-size: $im-font-size !important;
color: $im-text-color-light;
}
.uni-forms-item__label {
color: $im-text-color;
font-size: $im-font-size !important;
}
.uni-forms-item {
//margin-bottom: 8px !important;
}
.uni-easyinput__content-input {
font-size: $im-font-size !important;
}
.uni-easyinput__placeholder-class {
color: $im-text-color-lighter;
font-size: $im-font-size !important;;
}
.uni-easyinput__content-textarea {
font-size: $im-font-size !important;;
}
.uni-input-input:disabled {
color: $im-text-color-light;
}
.uni-forms-item.is-direction-top .uni-forms-item__label {
padding: 0 !important;
}
.uni-data-checklist .checklist-group .checklist-box .checklist-content .checklist-text {
font-size: $im-font-size !important;
}
.uni-card .uni-card__content {
color: unset !important;
padding: 10px 0 !important;
}
.uni-tag-text--small{
font-size: 10px !important;
font-weight: bolder !important;
}
.nav-bar {
height: 100rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
background-color: white;
border-bottom: 1px solid $im-border;
.nav-search {
flex: 1;
}
.nav-add {
cursor: pointer;
}
}
.bottom-btn {
margin: 40rpx 40rpx;
uni-button + uni-button {
margin-top: 20rpx;
}
}

11
im-uniapp/main.js

@ -13,6 +13,10 @@ import useFriendStore from '@/store/friendStore.js'
import useGroupStore from '@/store/groupStore.js'
import useConfigStore from '@/store/configStore.js'
import useUserStore from '@/store/userStore.js'
import barGroup from '@/components/bar/bar-group'
import arrowBar from '@/components/bar/arrow-bar'
import btnBar from '@/components/bar/btn-bar'
import switchBar from '@/components/bar/switch-bar'
//import VConsole from 'vconsole'
//new VConsole();
@ -23,13 +27,14 @@ 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);
app.use(pinia.createPinia());
app.component('bar-group', barGroup);
app.component('arrow-bar', arrowBar);
app.component('btn-bar', btnBar);
app.component('switch-bar', switchBar);
app.config.globalProperties.$http = request;
app.config.globalProperties.$wsApi = socketApi;
app.config.globalProperties.$msgType = messageType;

11
im-uniapp/manifest.json

@ -2,8 +2,8 @@
"name": "盒子IM",
"appid": "__UNI__69DD57A",
"description": "",
"versionName" : "3.0.0",
"versionCode" : 300,
"versionName": "3.1.0",
"versionCode": 3100,
"transformPx": false,
/* 5+App */
"app-plus": {
@ -45,7 +45,11 @@
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\" />"
],
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
"abiFilters": [
"armeabi-v7a",
"arm64-v8a",
"x86"
],
"minSdkVersion": 21
},
/* ios */
@ -128,4 +132,3 @@
}
}
/* ios */ /* SDK */

61
im-uniapp/pages.json

@ -7,59 +7,76 @@
"^u-([^-].*)": "@/uni_modules/uview-plus/components/u-$1/u-$1.vue"
}
},
"pages": [{
"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"
}
],
"globalStyle": {
"navigationBarTitleText": "盒子IM",
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#F0F0F0",
"backgroundColor": "#fdfdfd"
"navigationStyle": "custom",
"navigationBarBackgroundColor": "#f7f7f7",
"backgroundColor": "#f7f7f7"
},
"tabBar": {
"color": "#000000",
"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",

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

@ -1,9 +1,6 @@
<template>
<view class="page chat-box">
<view class="header">
<text class="title">{{title}}</text>
<uni-icons class="btn-side right" type="more-filled" size="30" @click="onShowMore()"></uni-icons>
</view>
<nav-bar back more @more="onShowMore">{{ title }}</nav-bar>
<view class="chat-msg" @click="switchChatTabBox('none', true)">
<scroll-view class="scroll-box" scroll-y="true" upper-threshold="200" @scrolltoupper="onScrollToTop"
:scroll-into-view="'chat-item-' + scrollMsgIdx">
@ -21,7 +18,7 @@
<scroll-view v-if="atUserIds.length > 0" class="chat-at-scroll-box" scroll-x="true" scroll-left="120">
<view class="chat-at-items">
<view v-for="m in atUserItems" class="chat-at-item">
<head-image :name="m.showNickName" :url="m.headImage" :size="50"></head-image>
<head-image :name="m.showNickName" :url="m.headImage" size="minier"></head-image>
</view>
</view>
</scroll-view>
@ -44,7 +41,8 @@
@touchend.prevent="sendTextMessage()" size="mini">发送</button>
</view>
<view class="chat-tab-bar" v-show="chatTabBox!='none' || (showKeyBoard && !isH5) " :style="{height:`${keyboardHeight}px`}">
<view class="chat-tab-bar" v-show="chatTabBox != 'none' || (showKeyBoard && !isH5)"
:style="{ height: `${keyboardHeight}px` }">
<view v-if="chatTabBox == 'tools'" class="chat-tools">
<view class="chat-tools-item">
<image-upload :maxCount="9" sourceType="album" :onBefore="onUploadImageBefore"
@ -768,7 +766,6 @@
<style lang="scss" scoped>
.chat-box {
position: relative;
border: #dddddd solid 1px;
display: flex;
flex-direction: column;
@ -778,16 +775,15 @@
align-items: center;
height: 60rpx;
padding: 5px;
background-color: #f8f8f8;
background-color: #f9f9f9;
line-height: 50px;
font-size: 36rpx;
border: #dddddd solid 1px;
font-size: $im-font-size-large;
box-shadow: $im-box-shadow-lighter;
z-index: 1;
.btn-side {
position: absolute;
line-height: 60rpx;
font-size: 28rpx;
cursor: pointer;
&.right {
@ -799,7 +795,6 @@
.chat-msg {
flex: 1;
padding: 0;
border: #dddddd solid 1px;
overflow: hidden;
position: relative;
background-color: white;
@ -813,12 +808,11 @@
display: flex;
align-items: center;
padding: 0 10rpx;
border: #dddddd solid 1px;
.icon-at {
font-size: 35rpx;
color: darkblue;
font-weight: 600;
font-size: $im-font-size-larger;
color: $im-color-primary;
font-weight: bold;
}
.chat-at-scroll-box {
@ -838,33 +832,38 @@
}
$icon-color: rgba(0, 0, 0, 0.88);
.send-bar {
display: flex;
align-items: center;
padding: 10rpx;
margin-bottom: 10rpx;
border: #dddddd solid 1px;
background-color: #f7f8fd;
//margin-bottom: 10rpx;
border-top: $im-border solid 1px;
background-color: $im-bg;
height: 80rpx;
//box-shadow: $im-box-shadow-lighter;
z-index: 1;
.iconfont {
font-size: 68rpx;
margin: 6rpx;
font-size: 60rpx;
margin: 0 10rpx;
color: $icon-color;
}
.chat-record {
flex: 1;
}
.send-text {
flex: 1;
overflow: auto;
padding: 20rpx;
padding: 14rpx 20rpx;
background-color: #fff;
border-radius: 20rpx;
font-size: 30rpx;
border-radius: 8rpx;
font-size: $im-font-size;
box-sizing: border-box;
margin: 0 10rpx;
.send-text-area {
width: 100%;
@ -880,28 +879,30 @@
.chat-tab-bar {
height: 500rpx;
padding: 20rpx;
background-color: #f8f8f8;
background-color: $im-bg;
.chat-tools {
display: flex;
flex-wrap: wrap;
padding-top: 20rpx;
.chat-tools-item {
width: 140rpx;
width: 25%;
padding: 16rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
align-items: center;
.tool-icon {
padding: 28rpx;
font-size: 60rpx;
padding: 26rpx;
font-size: 54rpx;
border-radius: 20%;
background-color: white;
color: black;
color: $icon-color;
&.active {
background-color: #ddd;
&:active {
background-color: $im-bg-active;
}
}
@ -919,10 +920,11 @@
.emotion-item-list {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.emotion-item {
width: 40px;
height: 40px;
width: 34px;
height: 34px;
text-align: center;
cursor: pointer;
padding: 6px;

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

@ -119,7 +119,6 @@
line-height: 50px;
font-size: 40rpx;
font-weight: 600;
border: #dddddd solid 1px;
.btn-side {
position: absolute;

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

@ -96,8 +96,5 @@
</script>
<style lang="scss" scoped>
.chat-web-view{
}
.chat-web-view {}
</style>

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

@ -1,11 +1,12 @@
<template>
<view class="tab-page">
<nav-bar search @search="showSearch = !showSearch">消息</nav-bar>
<view v-if="loading" class="chat-loading">
<loading :size="50" :mask="false">
<view>消息接收中...</view>
</loading>
</view>
<view class="nav-bar">
<view class="nav-bar" v-if="showSearch">
<view class="nav-search">
<uni-search-bar radius="100" v-model="searchText" cancelButton="none" placeholder="搜索"></uni-search-bar>
</view>
@ -15,22 +16,20 @@
</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>
</pop-menu>
<long-press-menu v-if="isShowChat(chat)" :items="menu.items" @select="onSelectMenu($event, index)">
<chat-item :chat="chat" :index="index" :active="menu.chatIdx == index"></chat-item>
</long-press-menu>
</view>
</scroll-view>
</view>
</template>
<script>
import useChatStore from '@/store/chatStore.js'
export default {
data() {
return {
showSearch: false,
searchText: "",
menu: {
show: false,
@ -120,34 +119,16 @@
<style scoped lang="scss">
.tab-page {
position: relative;
border: #dddddd solid 1px;
display: flex;
flex-direction: column;
.nav-bar {
padding: 2rpx 20rpx;
display: flex;
align-items: center;
background-color: white;
border-bottom: 1px solid #ddd;
height: 110rpx;
.nav-search {
flex: 1;
height: 110rpx;
}
}
.chat-tip {
position: absolute;
top: 400rpx;
padding: 50rpx;
line-height: 50rpx;
text-align: left;
color: darkblue;
font-size: 30rpx;
text-align: center;
color: $im-text-color-lighter;
}
.chat-loading {
@ -158,11 +139,10 @@
position: fixed;
top: 0;
z-index: 999;
color: blue;
color: $im-text-color-lighter;
.loading-box {
position: relative;
}
}

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

@ -1,33 +1,46 @@
<template>
<view class="page user-info">
<nav-bar back>用户信息</nav-bar>
<uni-card :is-shadow="false" is-full :border="false">
<view class="content">
<head-image :name="userInfo.nickName" :url="userInfo.headImageThumb"
:size="160" @click="onShowFullImage()"></head-image>
<head-image :name="userInfo.nickName" :url="userInfo.headImageThumb" :size="160"
@click="onShowFullImage()"></head-image>
<view class="info-item">
<view class="info-primary">
<text class="info-username">
{{ userInfo.userName }}
</text>
<uni-icons v-show="userInfo.sex==0" class="sex-boy" type="person-filled" size="20"
color="darkblue"></uni-icons>
<uni-icons v-show="userInfo.sex==1" class="sex-girl" type="person-filled" size="20"
color="darkred"></uni-icons>
<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>
<text>
昵称 {{userInfo.nickName}}
<view class="info-text">
<text class="label-text">
昵称:
</text>
<text>
签名 {{userInfo.signature}}
<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="line"></view>
<view class="btn-group">
<button class="btn" v-show="isFriend" type="primary" @click="onSendMessage()">发消息</button>
<button class="btn" v-show="!isFriend" type="primary" @click="onAddFriend()">加为好友</button>
<button class="btn" v-show="isFriend" type="warn" @click="onDelFriend()">删除好友</button>
</view>
</uni-card>
<bar-group>
<btn-bar v-show="isFriend" type="primary" title="发送消息" @click="onSendMessage()">
</btn-bar>
<btn-bar v-show="!isFriend" type="primary" title="加为好友" @click="onAddFriend()"></btn-bar>
<btn-bar v-show="isFriend" type="danger" title="删除好友" @click="onDelFriend()"></btn-bar>
</bar-group>
</view>
</template>
@ -162,31 +175,44 @@
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: 40rpx;
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-girl {
color: darkred;
}
}
.line {
margin: 20rpx;
border-bottom: 1px solid #aaaaaa;
}
.btn-group {
margin: 100rpx;
.btn{
margin-top: 20rpx;
}
}
}
</style>

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

@ -1,16 +1,18 @@
<template>
<view class="page friend-add">
<view class="search-bar">
<uni-search-bar v-model="searchText" radius="100" :focus="true" @confirm="onSearch()" @cancel="onCancel()"
placeholder="用户名/昵称"></uni-search-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>
<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"
:size="100"></head-image>
<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"
@ -79,17 +81,14 @@
<style scoped lang="scss">
.friend-add {
position: relative;
border: #dddddd solid 1px;
display: flex;
flex-direction: column;
.search-bar {
background: white;
}
.user-items {
position: relative;
flex: 1;
overflow: hidden;
.user-item {
height: 120rpx;
display: flex;
@ -103,8 +102,7 @@
.user-name {
flex: 1;
padding-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
font-size: $im-font-size;
line-height: 60rpx;
white-space: nowrap;
overflow: hidden;

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

@ -1,11 +1,11 @@
<template>
<view class="tab-page friend">
<view class="nav-bar">
<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>
</view>
<view class="nav-add" @click="onAddNewFriends()">
<uni-icons type="personadd" size="35"></uni-icons>
<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">
@ -15,8 +15,7 @@
<up-index-list :index-list="friendIdx">
<template v-for="(friends, i) in friendGroups">
<up-index-item>
<up-index-anchor :text="friendIdx[i]=='*'?'在线':friendIdx[i]"
bgColor="#f2f3fd"></up-index-anchor>
<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>
@ -24,7 +23,6 @@
</template>
</up-index-list>
</view>
</view>
</template>
@ -34,6 +32,7 @@
export default {
data() {
return {
showSearch: false,
searchText: ''
}
},
@ -106,40 +105,40 @@
<style lang="scss" scoped>
.friend {
position: relative;
border: #dddddd solid 1px;
display: flex;
flex-direction: column;
.friend-tip {
position: absolute;
top: 400rpx;
padding: 50rpx;
text-align: center;
line-height: 50rpx;
text-align: left;
color: darkblue;
font-size: 30rpx;
:deep(.u-index-anchor) {
height: 60rpx !important;
background-color: unset !important;
border-bottom: none !important;
}
.nav-bar {
padding: 2rpx 10rpx;
display: flex;
align-items: center;
background-color: white;
:deep(.u-index-anchor__text) {
color: $im-text-color !important;
}
.nav-search {
flex: 1;
:deep(.u-index-list__letter__item) {
width: 48rpx !important;
height: 48rpx !important;
}
.nav-add {
cursor: pointer;
: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;
border: #dddddd solid 1px;
overflow: hidden;
position: relative;

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

@ -1,28 +1,32 @@
<template>
<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 label="群聊头像:" name="headImage">
<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>
<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-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-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 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-forms-item label="群公告" name="notice">
<uni-easyinput type="textarea" v-model="group.notice" :disabled="!isOwner" placeholder="请输入群公告" />
</uni-forms-item>
</uni-forms>
<button type="primary" @click="submit()">提交</button>
</uni-card>
<button class="bottom-btn" type="primary" @click="submit()">提交</button>
</view>
</template>
@ -42,7 +46,6 @@
}
}
},
methods: {
submit() {
if (this.group.id) {
@ -138,7 +141,7 @@
<style lang="scss" scoped>
.group-edit {
padding: 20rpx;
//padding: 20rpx;
.group-image {
width: 200rpx;
@ -147,4 +150,8 @@
border-radius: 5%;
}
}
.avatar {
margin-top: -30px;
}
</style>

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

@ -1,54 +1,55 @@
<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">
<view class="member-items">
<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="100" :online="member.online" ></head-image>
<head-image :id="member.userId" :name="member.showNickName" :url="member.headImage" size="small"
:online="member.online"></head-image>
<view class="member-name">
<text>{{ member.showNickName }}</text>
</view>
</view>
</view>
<view class="invite-btn" @click="onInviteMember()">
<uni-icons type="plusempty" size="28" color="#888888"></uni-icons>
<uni-icons type="plusempty" size="20" color="#888888"></uni-icons>
</view>
</view>
<view class="member-more" @click="onShowMoreMmeber()">{{ `查看全部群成员${groupMembers.length}` }}></view>
</view>
<view class="group-detail">
<uni-section title="群聊名称:" titleFontSize="14px">
<uni-section title="群聊名称">
<template v-slot:right>
<text class="detail-text">{{ group.name }}</text>
</template>
</uni-section>
<uni-section title="群主:" titleFontSize="14px">
<uni-section title="群主">
<template v-slot:right>
<text class="detail-text">{{ ownerName }}</text>
</template>
</uni-section>
<uni-section title="群名备注:" titleFontSize="14px">
<uni-section title="群名备注">
<template v-slot:right>
<text class="detail-text"> {{ group.remarkGroupName }}</text>
</template>
</uni-section>
<uni-section title="我在本群的昵称:" titleFontSize="14px">
<uni-section title="我在本群的昵称">
<template v-slot:right>
<text class="detail-text"> {{ group.showNickName }}</text>
</template>
</uni-section>
<uni-section v-if="group.notice" title="群公告:" titleFontSize="14px">
<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>
<view v-if="!group.quit" class="btn-group">
<button class="btn" type="primary" @click="onSendMessage()">发消息</button>
<button class="btn" v-show="!isOwner" type="warn" @click="onQuitGroup()">退出群聊</button>
<button class="btn" v-show="isOwner" type="warn" @click="onDissolveGroup()">解散群聊</button>
</view>
<bar-group v-if="!group.quit">
<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>
</view>
</template>
@ -211,16 +212,16 @@
position: relative;
align-items: center;
padding-right: 5px;
background-color: #fafafa;
white-space: nowrap;
.member-name {
width: 100%;
flex: 1;
font-size: 14px;
overflow: hidden;
text-align: center;
white-space: nowrap;
padding-top: 8rpx;
font-size: $im-font-size-smaller;
}
}
@ -228,53 +229,38 @@
display: flex;
justify-content: center;
align-items: center;
width: 100rpx;
height: 100rpx;
width: 86rpx;
height: 86rpx;
margin: 10rpx;
border: #686868 dashed 2px;
border: $im-border solid 2rpx;
border-radius: 10%;
}
}
.member-more {
padding-top: 30rpx;
padding-top: 24rpx;
text-align: center;
font-size: 32rpx;
color: #333;
font-size: $im-font-size-small;
color: $im-text-color-lighter;
}
}
.group-detail {
margin-top: 30rpx;
padding: 30rpx;
padding: 20rpx 20rpx;
background: white;
.detail-text {
font-size: 28rpx;
font-weight: 600;
font-size: $im-font-size;
}
.group-edit {
padding-top: 30rpx;
text-align: center;
font-size: 32rpx;
color: #333;
font-size: $im-font-size-small;
color: $im-text-color-lighter;
}
}
.btn-group {
margin-top: 30rpx;
padding: 30rpx;
background: white;
.btn {
margin: 10rpx;
}
}
}
</style>

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

@ -1,28 +1,26 @@
<template>
<view class="page group-invite">
<view class="search-bar">
<uni-search-bar v-model="searchText" radius="100" cancelButton="none" placeholder="输入好友昵称搜索"></uni-search-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="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)">
<head-image :name="friend.nickName"
:online="friend.online" :url="friend.headImage"
:size="100"></head-image>
<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>
</view>
</scroll-view>
</view>
<view>
<button type="primary" :disabled="inviteSize==0" @click="onInviteFriends()">邀请({{inviteSize}}) </button>
</view>
<button class="bottom-btn" type="primary" :disabled="inviteSize == 0"
@click="onInviteFriends()">邀请({{ inviteSize }}) </button>
</view>
</template>
@ -122,7 +120,6 @@
<style lang="scss" scoped>
.group-invite {
position: relative;
border: #dddddd solid 1px;
display: flex;
flex-direction: column;
@ -141,6 +138,14 @@
background-color: white;
white-space: nowrap;
&.disabled {
background-color: $im-bg-active !important;
}
&.checked {
background-color: $im-color-primary-light-9;
}
.friend-name {
flex: 1;
padding-left: 20rpx;

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

@ -1,26 +1,24 @@
<template>
<view class="page group-member">
<view class="search-bar">
<uni-search-bar v-model="searchText" radius="100" cancelButton="none" placeholder="输入昵称搜索"></uni-search-bar>
<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"
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"
:size="100"></head-image>
<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
custom-style="background-color: #e30a0a;">
<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>
<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>
@ -112,7 +110,6 @@
<style scoped lang="scss">
.group-member {
position: relative;
border: #dddddd solid 1px;
display: flex;
flex-direction: column;
@ -136,9 +133,8 @@
align-items: center;
flex: 1;
padding-left: 20rpx;
font-size: 30rpx;
font-weight: 600;
line-height: 60rpx;
font-size: $im-font-size;
line-height: $im-font-size * 2;
white-space: nowrap;
overflow: hidden;

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

@ -1,13 +1,11 @@
<template>
<view class="tab-page group">
<view class="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 class="nav-add" @click="onCreateNewGroup()">
<uni-icons type="personadd" size="35"></uni-icons>
</view>
</view>
<view class="group-tip" v-if="groupStore.groups.length == 0">
温馨提示您现在还没有加入任何群聊点击右上方'+'按钮可以创建群聊哦~
@ -27,6 +25,7 @@
export default {
data() {
return {
showSearch: false,
searchText: ""
}
},
@ -40,32 +39,15 @@
})
}
}
}
</script>
<style lang="scss" scoped>
.group {
position: relative;
border: #dddddd solid 1px;
display: flex;
flex-direction: column;
.nav-bar {
padding: 2rpx 10rpx;
display: flex;
align-items: center;
background-color: white;
.nav-search {
flex: 1;
}
.nav-add {
cursor: pointer;
}
}
.group-tip {
position: absolute;
top: 400rpx;
@ -79,7 +61,6 @@
.group-items {
flex: 1;
padding: 0;
border: #dddddd solid 1px;
overflow: hidden;
position: relative;

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

@ -1,5 +1,5 @@
<template>
<view class="page login">
<view class="login">
<view class="title">欢迎登录</view>
<uni-forms class="form" :modelValue="loginForm" :rules="rules" validate-trigger="bind">
<uni-forms-item name="userName">
@ -74,10 +74,10 @@
.title {
padding-top: 150rpx;
padding-bottom: 50rpx;
color: royalblue;
color: $im-color-primary;
text-align: center;
font-size: 60rpx;
font-weight: 600;
font-size: 24px;
font-weight: bold;
}
.form {
@ -93,9 +93,9 @@
position: fixed;
width: 100%;
bottom: 100rpx;
color: royalblue;
color: $im-color-primary;
text-align: center;
font-size: 32rpx;
font-size: $im-font-size;
}
}
</style>

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

@ -1,29 +1,29 @@
<template>
<view class="page mine-edit">
<uni-forms ref="form" :modelValue="userInfo" label-position="top"
label-width="100%">
<uni-forms-item label="头像:" name="headImage">
<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-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-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">
<radio-group @change="onSexchange">
<label class="radio"><radio value="0" :checked="userInfo.sex==0" /></label>
<label class="radio"><radio value="1" :checked="userInfo.sex==1" /> </label>
</radio-group>
<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-forms-item label="签名" name="signature">
<uni-easyinput type="textarea" v-model="userInfo.signature" placeholder="编辑个性标签,展示我的独特态度" />
</uni-forms-item>
</uni-forms>
<button type="primary" @click="onSubmit()">提交</button>
</uni-card>
<button type="primary" class="bottom-btn" @click="onSubmit()">提交</button>
</view>
</template>
@ -69,11 +69,13 @@
<style scoped lang="scss">
.mine-edit {
padding: 20rpx;
.head-image {
width: 200rpx;
height: 200rpx;
}
}
.avatar {
margin-top: -30px;
}
</style>

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

@ -1,18 +1,20 @@
<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-forms-item label="原密码" name="oldPassword">
<uni-easyinput type="password" v-model="formData.oldPassword" />
</uni-forms-item>
<uni-forms-item label="新密码:" name="newPassword">
<uni-forms-item label="新密码" name="newPassword">
<uni-easyinput type="password" v-model="formData.newPassword" />
</uni-forms-item>
<uni-forms-item label="确认密码:" name="confirmPassword">
<uni-forms-item label="确认密码" name="confirmPassword">
<uni-easyinput type="password" v-model="formData.confirmPassword" />
</uni-forms-item>
<button type="primary" @click="onSubmit()">提交</button>
</uni-forms>
</uni-card>
<button class="bottom-btn" type="primary" @click="onSubmit()">提交</button>
</view>
</template>
@ -96,9 +98,4 @@
}
</script>
<style scoped lang="scss">
.mine-password {
padding: 20rpx;
}
</style>
<style scoped lang="scss"></style>

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

@ -1,33 +1,47 @@
<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>
<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>
<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>
<text>
昵称 {{userInfo.nickName}}
<view class="info-text">
<text class="label-text">
昵称:
</text>
<text>
签名 {{userInfo.signature}}
<text class="content-text">
{{ userInfo.nickName }}
</text>
</view>
<view class="info-arrow">></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 class="line"></view>
<view class="btn-group">
<button class="btn" type="primary" @click="onModifyPassword()">修改密码</button>
<button class="btn" type="warn" @click="onQuit()">退出</button>
</view>
</uni-card>
<bar-group>
<arrow-bar title="修改密码" @click="onModifyPassword()"></arrow-bar>
</bar-group>
<bar-group>
<btn-bar title="退出登陆" type="danger" @click="onQuit()"></btn-bar>
</bar-group>
</view>
</template>
@ -72,7 +86,7 @@
<style scoped lang="scss">
.mine {
.content {
height: 200rpx;
//height: 200rpx;
display: flex;
align-items: center;
justify-content: space-between;
@ -85,16 +99,36 @@
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: 40rpx;
font-size: $im-font-size-large;
font-weight: 600;
}
.icon-man {
color: darkblue;
color: $im-text-color;
font-size: $im-font-size-large;
padding-left: 10rpx;
}
.icon-girl {
@ -106,22 +140,10 @@
.info-arrow {
width: 50rpx;
font-size: 30rpx;
font-weight: 600;
}
position: relative;
left: 30rpx;
}
.line {
margin: 20rpx;
border-bottom: 1px solid #aaaaaa;
}
.btn-group {
margin: 100rpx;
.btn {
margin-top: 20rpx;
}
}
}
</style>

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

@ -14,7 +14,7 @@
<uni-forms-item name="corfirmPassword" label="确认密码">
<uni-easyinput type="password" v-model="dataForm.corfirmPassword" placeholder="确认密码" />
</uni-forms-item>
<button class="btn-submit" @click="submit" type="warn">注册并登陆</button>
<button class="btn-submit" @click="submit" type="primary">注册并登陆</button>
</uni-forms>
<navigator class="nav-login" url="/pages/login/login">
返回登陆页面
@ -70,6 +70,7 @@
},
methods: {
submit() {
this.$refs.form.validate().then(() => {
this.$http({
url: '/register',
data: this.dataForm,
@ -81,6 +82,7 @@
})
this.login();
})
})
},
login() {
const loginForm = {
@ -114,9 +116,9 @@
.title {
padding-top: 150rpx;
padding-bottom: 50rpx;
color: royalblue;
color: $im-color-primary;
text-align: center;
font-size: 60rpx;
font-size: 24px;
font-weight: 600;
}
@ -133,7 +135,7 @@
position: fixed;
width: 100%;
bottom: 100rpx;
color: royalblue;
color: $im-color-primary;
text-align: center;
font-size: 32rpx;
}

BIN
im-uniapp/static/logo/logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 238 KiB

BIN
im-uniapp/static/tarbar/mine.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
im-uniapp/static/tarbar/mine_active.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 8.4 KiB

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',

46
im-uniapp/uni.scss

@ -1,5 +1,51 @@
@import '@/uni_modules/uview-plus/theme.scss';
@import '@/uni_modules/uni-scss/variables.scss';
@import '@/im-var.scss';
// 修改uni-ui主题
$uni-primary: $im-color-primary;
$uni-success: $im-color-success;
$uni-warning: $im-color-warning;
$uni-error: $im-color-danger;
$uni-info: $im-color-info;
// $uni-main-color: #3a3a3a; // 主要文字
// $uni-base-color: #6a6a6a; // 常规文字
// $uni-secondary-color: #909399; // 次要文字
// $uni-extra-color: #c7c7c7; // 辅助说明
// // 边框颜色
// $uni-border-1: #F0F0F0;
// $uni-border-2: #EDEDED;
// $uni-border-3: #DCDCDC;
// $uni-border-4: #B9B9B9;
// // 常规色
// $uni-black: #000000;
// $uni-white: #ffffff;
// $uni-transparent: rgba($color: #000000, $alpha: 0);
// // 背景色
// $uni-bg-color: #f7f7f7;
// /* 水平间距 */
// $uni-spacing-sm: 8px;
// $uni-spacing-base: 15px;
// $uni-spacing-lg: 30px;
// // 阴影
// $uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5);
// $uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2);
// $uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5);
// // 蒙版
// $uni-mask: rgba($color: #000000, $alpha: 0.4);
/**************************************************/
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;

BIN
im-uniapp/unpackage/res/icons/1024x1024.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

BIN
im-uniapp/unpackage/res/icons/120x120.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

BIN
im-uniapp/unpackage/res/icons/144x144.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

BIN
im-uniapp/unpackage/res/icons/152x152.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
im-uniapp/unpackage/res/icons/167x167.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
im-uniapp/unpackage/res/icons/180x180.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
im-uniapp/unpackage/res/icons/192x192.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
im-uniapp/unpackage/res/icons/20x20.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

BIN
im-uniapp/unpackage/res/icons/29x29.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

BIN
im-uniapp/unpackage/res/icons/40x40.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
im-uniapp/unpackage/res/icons/58x58.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
im-uniapp/unpackage/res/icons/60x60.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
im-uniapp/unpackage/res/icons/72x72.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

BIN
im-uniapp/unpackage/res/icons/76x76.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
im-uniapp/unpackage/res/icons/80x80.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

BIN
im-uniapp/unpackage/res/icons/87x87.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

BIN
im-uniapp/unpackage/res/icons/96x96.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

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()

24
im-web/package.json

@ -8,18 +8,18 @@
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^1.1.3",
"core-js": "^3.6.5",
"element-ui": "^2.15.10",
"js-audio-recorder": "^1.0.7",
"localforage": "^1.10.0",
"sass": "^1.47.0",
"sass-loader": "^10.1.1",
"vue": "^2.6.11",
"vue-axios": "^3.5.0",
"vue-router": "^3.3.3",
"vuex": "^3.6.2",
"vuex-persist": "^3.1.3"
"axios": "1.7.7",
"core-js": "3.38.1",
"element-ui": "2.15.14",
"js-audio-recorder": "1.0.7",
"localforage": "1.10.0",
"sass": "1.32.12",
"sass-loader": "10.1.1",
"vue": "2.7.16",
"vue-axios": "3.5.2",
"vue-router": "3.6.5",
"vuex": "3.6.2",
"vuex-persist": "3.1.3"
},
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.12",

BIN
im-web/public/logo.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 141 KiB

95
im-web/src/App.vue

@ -12,104 +12,13 @@
</script>
<style lang="scss">
@import './assets/style/global.css';
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
position: absolute;
height: 100%;
width: 100%;
}
.el-message {
z-index: 99999999 !important;
}
.el-scrollbar__thumb {
background-color: #A0A8AF !important;
}
.el-dialog {
border-radius: 8px !important;
overflow: hidden !important;
}
.el-dialog__header {
background-color: #5870e6 !important;
}
.el-dialog__title {
color: #f8f8f8 !important;
}
.el-dialog__close {
color: white !important;
font-size: 20px;
}
.el-checkbox__inner {
border-radius: 50% !important;
}
.el-input__inner {
border-radius: 5px !important;
border: #587FF0 1px solid !important;
}
.el-textarea__inner{
border-radius: 5px !important;
border: #587FF0 1px solid !important;
}
.el-icon-search {
color:#587FF0 !important;
}
.el-button--primary {
background-color: #687Ff0 !important;
border-color: #687Ff0 !important;
}
.el-button--success {
background-color: #4cae1b !important;
border-color: #4cae1b !important;
}
.el-button--danger {
background-color: #ea4949 !important;
border-color: #ea4949 !important;
}
.el-button {
padding: 8px 15px !important;
}
.el-checkbox {
display: flex;
align-items: center;
//
.el-checkbox__inner {
width: 20px;
height: 20px;
//
&::after {
height: 12px;
left: 7px;
}
}
//
.el-checkbox__input.is-checked+.el-checkbox__label {
color: #333333;
}
.el-checkbox__label {
line-height: 20px;
padding-left: 8px;
}
color: var(--im-text-color);
font-family: var(--im-font-family);
}
</style>

5
im-web/src/api/camera.js

@ -15,10 +15,7 @@ ImCamera.prototype.openVideo = function() {
this.close()
}
let constraints = {
video: {
with: window.screen.width,
height: window.screen.height
},
video: true,
audio: {
echoCancellation: true, //音频开启回音消除
noiseSuppression: true // 开启降噪

2
im-web/src/api/emotion.js

@ -19,7 +19,7 @@ let textToImg = (emoText) => {
return emoText;
}
let url = require(`@/assets/emoji/${idx}.gif`);
return `<img src="${url}" style="width:35px;height:35px;vertical-align:bottom;"/>`
return `<img src="${url}" style="width:32px;height:32px;vertical-align:bottom;"/>`
}
let textToUrl = (emoText) => {

BIN
im-web/src/assets/image/online_app.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

BIN
im-web/src/assets/image/online_web.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

112
im-web/src/assets/style/element.scss

@ -0,0 +1,112 @@
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
// 文字
$--font-family: Microsoft YaHei, 'Avenir', Helvetica, Arial, sans-serif;
@import "thems";
@import "~element-ui/packages/theme-chalk/src/index";
.el-message {
z-index: 99999999 !important;
background: #fff !important;
box-shadow: 0 4px 12px 0 rgb(0 0 0 / 15%);
border: none !important;
min-width: unset !important;
border-radius: 3px !important;
padding: 14px 18px 14px 16px !important;
.el-message__content {
color: #000 !important;
}
}
.el-scrollbar__thumb {
background-color: #A0A8AF !important;
}
.el-dialog__title {
font-size: var(--im-font-size-larger);
color: var(--im-text-color);
}
.el-dialog__header {
padding: 12px 18px !important;
}
.el-dialog__headerbtn {
top: 15px;
right: 20px;
font-size: 18px;
}
.el-checkbox__inner {
border-radius: 50% !important;
}
.el-button--success {
//background-color: #688758 !important;
//border-color: #4cae1b !important;
}
.el-button--danger {
//background-color: #ea4949 !important;
//border-color: #ea4949 !important;
}
.el-button {
padding: 8px 15px !important;
}
.el-checkbox {
display: flex;
align-items: center;
//修改选中框的大小
.el-checkbox__inner {
width: 16px;
height: 16px;
//修改选中框中的对勾的大小和位置
&::after {
height: 7px;
left: 5px;
top: 2px;
}
}
// 修改点击文字颜色不变
.el-checkbox__input.is-checked + .el-checkbox__label {
color: #333333;
}
.el-checkbox__label {
line-height: 20px;
padding-left: 8px;
}
}
.el-form-item {
margin-bottom: 15px !important;
}
.el-input--small {
font-size: $--font-size-base;
}
.el-input__inner {
padding: 0 10px;
}
.el-textarea__inner {
padding: 5px 10px;
font-family: $--font-family;
}
.el-tag--mini {
height: 18px;
padding: 0 2px;
line-height: 16px;
border-radius: 2px;
}

43
im-web/src/assets/style/global.css

@ -1,43 +0,0 @@
@charset "UTF-8";
html {
height: 100%;
overflow: hidden;
}
body {
height: 100%;
margin: 0;
overflow: hidden;
}
section {
height: 100%;
}
.el-dialog__body{
padding: 10px 15px !important;
}
::-webkit-scrollbar {
width: 6px;
height: 1px;
}
::-webkit-scrollbar-thumb {
/*滚动条里面小方块*/
border-radius: 2px;
-webkit-box-shadow: inset 0 0 5px rgba(0, 0, 0, 0.2);
background: #535353;
}
::-webkit-scrollbar-track {
/*滚动条里面轨道*/
-webkit-box-shadow: inset 0 0 5px transparent;
border-radius: 2px;
background: #ededed;
}
/*# sourceMappingURL=v-im.cssss.map */

91
im-web/src/assets/style/im.scss

@ -0,0 +1,91 @@
@charset "UTF-8";
@import "element";
// im全局样式变量
:root {
// 主色
--im-color-primary: #{$--color-primary};
--im-color-primary-light-1: #{$--color-primary-light-1};
--im-color-primary-light-2: #{$--color-primary-light-2};
--im-color-primary-light-3: #{$--color-primary-light-3};
--im-color-primary-light-4: #{$--color-primary-light-4};
--im-color-primary-light-5: #{$--color-primary-light-5};
--im-color-primary-light-6: #{$--color-primary-light-6};
--im-color-primary-light-7: #{$--color-primary-light-7};
--im-color-primary-light-8: #{$--color-primary-light-8};
--im-color-primary-light-9: #{$--color-primary-light-9};
--im-color-sucess: #{$--color-success};
--im-color-warning: #{$--color-warning};
--im-color-danger: #{$--color-danger};
--im-color-info: #{$--color-info};
// 文字颜色
--im-text-color: #{$--color-text-regular};
--im-text-color-light: #999999;
--im-text-color-lighter: #C0C4CC;
// 文字大小
--im-font-size: #{$--font-size-base};
--im-font-size-small: #{$--font-size-small};
--im-font-size-smaller: #{$--font-size-extra-small};
--im-font-size-large: #{$--font-size-medium};
--im-font-size-larger: #{$--font-size-large};
--im-font-family: #{$--font-family};
// 边框颜色
--im-border: 1px solid #EBEEF5;
// 阴影
--im-box-shadow: #{$--box-shadow-base};
--im-box-shadow-light: #{$--box-shadow-light};
--im-box-shadow-lighter: 0px 0px 6px rgba(0, 0, 0, .12);
--im-box-shadow-dark: 0px 16px 48px 16px rgba(0, 0, 0, .08), 0px 12px 32px rgba(0, 0, 0, .12), 0px 8px 16px -8px rgba(0, 0, 0, .16);
// 背景色
--im-background: #F3F3F3;
--im-background-active: #F1F1F1;
--im-background-active-dark: #E9E9E9;
}
html {
height: 100%;
overflow: hidden;
}
body {
height: 100%;
margin: 0;
overflow: hidden;
}
section {
height: 100%;
}
.el-dialog__body {
padding: 10px 20px !important;
}
// 滚动条样式
::-webkit-scrollbar {
width: 8px;
height: 1px;
}
::-webkit-scrollbar-thumb {
border-radius: 4px;
background: hsla(0, 0%, 73%, .5);
}
::-webkit-scrollbar-track {
border-radius: 4px;
}
.search-input {
.el-input__inner {
border: unset !important;
}
}

6
im-web/src/assets/style/thems.scss

@ -0,0 +1,6 @@
// 主题色
$--color-primary: #2830d3;
//$--color-primary: #687ff0;
//$--color-primary: #096bff;
$--font-size-base: 14px;
$--color-text-regular: #000000;

8
im-web/src/components/chat/ChatAtBox.vue

@ -123,9 +123,9 @@
position: fixed;
width: 200px;
height: 300px;
border: 1px solid #53a0e79c;
border-radius: 5px;
background-color: #f5f5f5;
box-shadow: 0px 0px 10px #ccc;
//border: 1px solid #53a0e79c;
//border-radius: 5px;
background-color: #fff;
box-shadow: var(--im-box-shadow);
}
</style>

66
im-web/src/components/chat/ChatBox.vue

@ -1,7 +1,7 @@
<template>
<div class="chat-box" @click="closeRefBox()" @mousemove="readedMessage()">
<el-container>
<el-header height="56px">
<el-header height="50px">
<span>{{ title }}</span>
<span title="群聊信息" v-show="this.chat.type == 'GROUP'" class="btn-side el-icon-more"
@click="showSide = !showSide"></span>
@ -23,7 +23,7 @@
</ul>
</div>
</el-main>
<el-footer height="240px" class="im-chat-footer">
<el-footer height="220px" class="im-chat-footer">
<div class="chat-tool-bar">
<div title="表情" class="icon iconfont icon-emoji" ref="emotion"
@click.stop="showEmotionBox()">
@ -61,12 +61,13 @@
<ChatInput :ownerId="group.ownerId" ref="chatInputEditor" :group-members="groupMembers"
@submit="sendMessage" />
<div class="send-btn-area">
<el-button type="primary" size="small" @click="notifySend()">发送</el-button>
<el-button type="primary" icon="el-icon-s-promotion"
@click="notifySend()">发送</el-button>
</div>
</div>
</el-footer>
</el-container>
<el-aside class="chat-group-side-box" width="300px" v-if="showSide">
<el-aside class="chat-group-side-box" width="260px" v-if="showSide">
<chat-group-side :group="group" :groupMembers="groupMembers" @reload="loadGroup(group.id)">
</chat-group-side>
</el-aside>
@ -667,29 +668,30 @@
.chat-box {
position: relative;
width: 100%;
background: #f8f8f8;
border: #dddddd solid 1px;
background: #fff;
.el-header {
padding: 3px;
background-color: white;
display: flex;
justify-content: space-between;
padding: 0 12px;
line-height: 50px;
font-size: 20px;
font-weight: 600;
border-bottom: 1px #ddd solid;
font-size: var(--im-font-size-larger);
border-bottom: var(--im-border);
.btn-side {
position: absolute;
right: 20px;
line-height: 50px;
font-size: 25px;
font-size: 20px;
cursor: pointer;
color: var(--im-text-color-light);
}
}
.im-chat-main {
padding: 0;
background-color: white;
background-color: #fff;
.im-chat-box {
>ul {
@ -711,36 +713,34 @@
display: flex;
position: relative;
width: 100%;
height: 40px;
height: 36px;
text-align: left;
box-sizing: border-box;
border-top: #ccc solid 1px;
padding: 2px;
background-color: #f8faff;
border-top: var(--im-border);
padding: 4px 2px 2px 8px;
>div {
font-size: 22px;
cursor: pointer;
color: black;
line-height: 30px;
width: 30px;
height: 30px;
text-align: center;
border-radius: 3px;
margin: 3px 5px;
color: #0f46ae;
&:hover {
font-weight: 600;
color: #042259;
}
border-radius: 2px;
margin-right: 8px;
color: #999;
transition: 0.3s;
&.chat-tool-active {
color: white;
background-color: #195ee2;
font-weight: 600;
color: var(--im-color-primary);
background-color: #ddd;
}
}
>div:hover {
color: #333;
}
}
.send-content-area {
@ -757,7 +757,6 @@
flex: 1;
resize: none;
font-size: 16px;
color: black;
outline: none;
text-align: left;
@ -820,15 +819,16 @@
.send-btn-area {
padding: 10px;
position: absolute;
bottom: 0;
right: 0;
bottom: 4px;
right: 6px;
}
}
}
.chat-group-side-box {
border: #dddddd solid 1px;
animation: rtl-drawer-in .3s 1ms;
border-left: var(--im-border);
//animation: rtl-drawer-in .3s 1ms;
}
}
</style>

13
im-web/src/components/chat/ChatGroupMember.vue

@ -42,30 +42,19 @@ export default {
<style lang="scss">
.chat-group-member {
display: flex;
margin-bottom: 1px;
position: relative;
padding: 0 5px;
align-items: center;
background-color: #fafafa;
white-space: nowrap;
box-sizing: border-box;
&:hover {
background-color: #F8FAFF;
}
&.active {
background-color: #E8F2FF;
}
.member-name {
padding-left: 10px;
height: 100%;
text-align: left;
white-space: nowrap;
overflow: hidden;
font-size: 14px;
font-weight: 600;
font-size: var(--im-font-size);
}
}
</style>

4
im-web/src/components/chat/ChatGroupReaded.vue

@ -130,17 +130,13 @@ export default {
.chat-group-readed {
position: fixed;
box-shadow: 0px 0px 10px #ccc;
width: 300px;
background-color: #fafafa;
border-radius: 8px;
.scroll-box {
height: 400px;
}
.arrow-left {
position: absolute;
left: -15px;
width: 0;

69
im-web/src/components/chat/ChatGroupSide.vue

@ -1,27 +1,27 @@
<template>
<div class="chat-group-side">
<div v-show="!group.quit" class="group-side-search">
<el-input placeholder="搜索群成员" v-model="searchText">
<el-input placeholder="搜索群成员" v-model="searchText" size="small">
<i class="el-icon-search el-input__icon" slot="prefix"> </i>
</el-input>
</div>
<el-scrollbar class="group-side-scrollbar">
<div class="group-side-scrollbar">
<div v-show="!group.quit" class="group-side-member-list">
<div class="group-side-invite">
<div class="invite-member-btn" title="邀请好友进群聊" @click="showAddGroupMember = true">
<i class="el-icon-plus"></i>
</div>
<div class="invite-member-text">邀请</div>
<add-group-member :visible="showAddGroupMember" :groupId="group.id" :members="groupMembers" @reload="$emit('reload')"
@close="showAddGroupMember=false"></add-group-member>
<add-group-member :visible="showAddGroupMember" :groupId="group.id" :members="groupMembers"
@reload="$emit('reload')" @close="showAddGroupMember = false"></add-group-member>
</div>
<div v-for="(member) in groupMembers" :key="member.id">
<group-member class="group-side-member" v-show="!member.quit && member.showNickName.includes(searchText)" :member="member"
:showDel="false"></group-member>
<group-member class="group-side-member" v-show="!member.quit && member.showNickName.includes(searchText)"
:member="member" :showDel="false"></group-member>
</div>
</div>
<el-divider v-if="!group.quit" content-position="center"></el-divider>
<el-form labelPosition="top" class="group-side-form" :model="group">
<el-form labelPosition="top" class="group-side-form" :model="group" size="small">
<el-form-item label="群聊名称">
<el-input v-model="group.name" disabled maxlength="20"></el-input>
</el-form-item>
@ -29,22 +29,21 @@
<el-input :value="ownerName" disabled></el-input>
</el-form-item>
<el-form-item label="群公告">
<el-input v-model="group.notice" disabled type="textarea" maxlength="1024" placeholder="群主未设置"></el-input>
<el-input v-model="group.notice" disabled type="textarea" maxlength="1024"></el-input>
</el-form-item>
<el-form-item label="备注">
<el-input v-model="group.remarkGroupName" :disabled="!editing" :placeholder="group.name" maxlength="20"></el-input>
<el-input v-model="group.remarkGroupName" :disabled="!editing" maxlength="20"></el-input>
</el-form-item>
<el-form-item label="我在本群的昵称">
<el-input v-model="group.remarkNickName" :disabled="!editing" maxlength="20"
:placeholder="$store.state.userStore.userInfo.nickName" ></el-input>
<el-input v-model="group.remarkNickName" :disabled="!editing" maxlength="20"></el-input>
</el-form-item>
<div v-show="!group.quit" class="btn-group">
<el-button v-show="editing" type="success" @click="onSaveGroup()">提交</el-button>
<el-button v-show="!editing" type="primary" @click="editing=!editing">编辑</el-button>
<el-button v-if="editing" type="success" @click="onSaveGroup()">保存</el-button>
<el-button v-if="!editing" type="primary" @click="editing = !editing">编辑</el-button>
<el-button type="danger" v-show="!isOwner" @click="onQuit()">退出群聊</el-button>
</div>
</el-form>
</el-scrollbar>
</div>
</div>
</template>
@ -93,6 +92,7 @@
method: "put",
data: vo
}).then((group) => {
this.editing = !this.editing
this.$store.commit("updateGroup", group);
this.$emit('reload');
this.$message.success("修改成功");
@ -133,16 +133,32 @@
.chat-group-side {
position: relative;
.group-side-search {
padding: 10px;
}
.group-side-scrollbar {
overflow: auto;
}
.el-divider--horizontal {
margin: 0;
}
.el-form-item {
margin-bottom: 0px !important;
}
.group-side-member-list {
padding: 10px;
display: flex;
align-items: center;
flex-wrap: wrap;
font-size: 16px;
font-size: 14px;
text-align: center;
.group-side-member {
margin-left: 15px;
margin-left: 5px;
}
.group-side-invite {
@ -150,14 +166,14 @@
flex-direction: column;
align-items: center;
width: 50px;
margin-left: 15px;
margin-left: 5px;
.invite-member-btn {
width: 100%;
height: 50px;
line-height: 50px;
border: #cccccc solid 1px;
font-size: 25px;
width: 38px;
height: 38px;
line-height: 38px;
border: var(--im-border);
font-size: 14px;
cursor: pointer;
box-sizing: border-box;
@ -167,7 +183,7 @@
}
.invite-member-text {
font-size: 16px;
font-size: 12px;
text-align: center;
width: 100%;
height: 30px;
@ -197,8 +213,15 @@
}
}
.el-input__inner,
.el-textarea__inner {
color: var(--im-text-color) !important;
}
.btn-group {
text-align: center;
margin-top: 12px;
}
}

9
im-web/src/components/chat/ChatHistory.vue

@ -1,12 +1,11 @@
<template>
<el-drawer title="聊天历史记录" size="700px" :visible.sync="visible" direction="rtl" :before-close="onClose">
<div class="chat-history" v-loading="loading"
element-loading-text="拼命加载中">
<div class="chat-history" v-loading="loading" element-loading-text="拼命加载中">
<el-scrollbar class="chat-history-scrollbar" ref="scrollbar" id="historyScrollbar">
<ul>
<li v-for="(msgInfo, idx) in messages" :key="idx">
<chat-message-item :mode="2" :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)" :showName="showName(msgInfo)"
:msgInfo="msgInfo" :menu="false">
<chat-message-item :mode="2" :mine="msgInfo.sendId == mine.id" :headImage="headImage(msgInfo)"
:showName="showName(msgInfo)" :msgInfo="msgInfo" :menu="false">
</chat-message-item>
</li>
</ul>
@ -155,9 +154,11 @@
.chat-history-scrollbar {
flex: 1;
.el-scrollbar__thumb {
background-color: #555555;
}
ul {
padding: 20px;

9
im-web/src/components/chat/ChatInput.vue

@ -482,10 +482,10 @@
bottom: 0;
outline: none;
padding: 5px;
line-height: 30px;
font-size: 16px;
line-height: 1.5;
font-size: var(--im-font-size);
text-align: left;
overflow-y: scroll;
overflow-y: auto;
// bug
>div:before {
@ -544,15 +544,12 @@
.file-size {
font-size: 14px;
font-weight: 600;
color: black;
}
}
}
.chat-at-user {
color: #00f;
font-weight: 600;
border-radius: 3px;
}
}

48
im-web/src/components/chat/ChatItem.vue

@ -1,8 +1,8 @@
<template>
<div class="chat-item" :class="active ? 'active' : ''" @contextmenu.prevent="showRightMenu($event)">
<div class="chat-left">
<head-image :url="chat.headImage" :name="chat.showName" :size="45"
:id="chat.type=='PRIVATE'?chat.targetId:0"></head-image>
<head-image :url="chat.headImage" :name="chat.showName" :size="42"
:id="chat.type == 'PRIVATE' ? chat.targetId : 0" :isShowUserInfo="false"></head-image>
<div v-show="chat.unreadCount > 0" class="unread-text">{{ chat.unreadCount }}</div>
</div>
<div class="chat-right">
@ -11,9 +11,7 @@
<div>{{ chat.showName }}</div>
<el-tag v-if="chat.type == 'GROUP'" size="mini" effect="dark"></el-tag>
</div>
<div class="chat-time-text">{{ showTime }}</div>
</div>
<div class="chat-content">
<div class="chat-at-text">{{ atText }}</div>
@ -21,8 +19,8 @@
<div class="chat-content-text" v-html="$emo.transform(chat.lastContent)"></div>
</div>
</div>
<right-menu v-show="rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items" @close="rightMenu.show=false"
@select="onSelectMenu"></right-menu>
<right-menu v-show="rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items"
@close="rightMenu.show = false" @select="onSelectMenu"></right-menu>
</div>
</template>
@ -112,34 +110,32 @@
.chat-item {
height: 50px;
display: flex;
margin-bottom: 1px;
position: relative;
padding: 5px 10px;
align-items: center;
background-color: white;
background-color: var(--im-background);
white-space: nowrap;
color: black;
cursor: pointer;
&:hover {
background-color: #F8FAFF;
background-color: var(--im-background-active);
}
&.active {
background-color: #F4F9FF;
background-color: var(--im-background-active-dark);
}
.chat-left {
position: relative;
display: flex;
width: 45px;
height: 45x;
justify-content: center;
align-items: center;
.unread-text {
position: absolute;
background-color: #f56c6c;
right: -5px;
top: -5px;
right: -4px;
top: -8px;
color: white;
border-radius: 30px;
padding: 1px 5px;
@ -161,19 +157,20 @@
.chat-name {
display: flex;
line-height: 25px;
height: 25px;
line-height: 20px;
height: 20px;
.chat-name-text {
flex: 1;
display: flex;
align-items: center;
font-size: 15px;
font-weight: 600;
font-size: var(--im-font-size);
white-space: nowrap;
overflow: hidden;
.el-tag {
min-width: 22px;
text-align: center;
background-color: #2830d3;
border-radius: 10px;
border: 0;
@ -186,11 +183,10 @@
}
}
.chat-time-text {
font-size: 13px;
font-size: var(--im-font-size-smaller);
text-align: right;
color: #888888;
color: var(--im-text-color-light);
white-space: nowrap;
overflow: hidden;
padding-left: 10px;
@ -203,11 +199,12 @@
.chat-at-text {
color: #c70b0b;
font-size: 12px;
font-size: var(--im-font-size-smaller);
}
.chat-send-name {
font-size: 13px;
font-size: var(--im-font-size-small);
color: var(--im-text-color-light);
}
@ -216,7 +213,8 @@
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
font-size: 13px;
font-size: var(--im-font-size-small);
color: var(--im-text-color-light);
img {
width: 20px !important;

78
im-web/src/components/chat/ChatMessageItem.vue

@ -9,7 +9,7 @@
</div>
<div class="chat-msg-normal" v-if="isNormal" :class="{ 'chat-msg-mine': mine }">
<div class="head-image">
<head-image :name="showName" :size="40" :url="headImage" :id="msgInfo.sendId"></head-image>
<head-image :name="showName" :size="38" :url="headImage" :id="msgInfo.sendId"></head-image>
</div>
<div class="chat-msg-content">
<div v-show="mode == 1 && msgInfo.groupId && !msgInfo.selfSend" class="chat-msg-top">
@ -213,13 +213,14 @@
.chat-msg-tip {
line-height: 50px;
font-size: 14px;
font-size: var(--im-font-size-small);
color: var(--im-text-color-light);
}
.chat-msg-normal {
position: relative;
font-size: 0;
padding-left: 60px;
padding-left: 48px;
min-height: 50px;
margin-top: 10px;
@ -244,8 +245,8 @@
.chat-msg-top {
display: flex;
flex-wrap: nowrap;
color: #333;
font-size: 14px;
color: var(--im-text-color-light);
font-size: var(--im-font-size);
line-height: 20px;
span {
@ -256,18 +257,17 @@
.chat-msg-bottom {
display: inline-block;
padding-right: 300px;
padding-left: 5px;
.chat-msg-text {
display: block;
position: relative;
line-height: 26px;
margin-top: 3px;
padding: 7px;
background-color: #eee;
//margin-top: 3px;
padding: 6px 10px;
background-color: var(--im-background);
border-radius: 10px;
color: black;
display: block;
font-size: 14px;
font-size: var(--im-font-size);
text-align: left;
white-space: pre-wrap;
word-break: break-all;
@ -277,7 +277,6 @@
position: absolute;
left: -10px;
top: 13px;
z-index: -1;
width: 0;
height: 0;
border-style: solid dashed dashed;
@ -298,9 +297,7 @@
min-height: 150px;
max-width: 400px;
max-height: 300px;
border: #dddddd solid 1px;
border: 5px solid #ccc;
border-radius: 6px;
border-radius: 8px;
cursor: pointer;
}
@ -312,17 +309,15 @@
flex-direction: row;
align-items: center;
cursor: pointer;
padding-bottom: 5px;
margin-bottom: 2px;
.chat-file-box {
display: flex;
flex-wrap: nowrap;
align-items: center;
min-height: 80px;
box-shadow: 5px 5px 2px #c0c0c0;
border: #dddddd solid 1px;
border-radius: 6px;
background-color: #eeeeee;
min-height: 60px;
box-shadow: var(--im-box-shadow-light);
border-radius: 4px;
padding: 10px 15px;
.chat-file-info {
@ -330,21 +325,26 @@
height: 100%;
text-align: left;
font-size: 14px;
margin-right: 10px;
.chat-file-name {
display: inline-block;
min-width: 150px;
max-width: 300px;
font-size: 16px;
font-weight: 600;
margin-bottom: 15px;
min-width: 160px;
max-width: 220px;
font-size: 14px;
margin-bottom: 4px;
white-space: pre-wrap;
word-break: break-all;
}
.chat-file-size {
font-size: var(--im-font-size-smaller);
color: var(--im-text-color-light);
}
}
.chat-file-icon {
font-size: 50px;
font-size: 44px;
color: #d42e07;
}
}
@ -384,32 +384,29 @@
.chat-readed {
font-size: 12px;
color: #888;
font-weight: 600;
color: var(--im-text-color-light);
}
.chat-unread {
font-size: 12px;
color: #f23c0f;
font-weight: 600;
font-size: var(--im-font-size-smaller);
color: var(--im-color-danger);
}
}
.chat-receipt {
font-size: 13px;
color: blue;
font-size: var(--im-font-size-smaller);
cursor: pointer;
color: var(--im-text-color-light);
.icon-ok {
font-size: 20px;
color: #329432;
color: var(--im-color-sucess);
}
}
.chat-at-user {
padding: 2px 5px;
border-radius: 3px;
font-weight: 600;
cursor: pointer;
}
}
@ -419,7 +416,7 @@
&.chat-msg-mine {
text-align: right;
padding-left: 0;
padding-right: 60px;
padding-right: 48px;
.head-image {
left: auto;
@ -440,18 +437,17 @@
.chat-msg-bottom {
padding-left: 180px;
padding-right: 0;
padding-right: 5px;
.chat-msg-text {
margin-left: 10px;
background-color: rgb(88, 127, 240);
background-color: var(--im-color-primary-light-2);
color: #fff;
vertical-align: top;
&:after {
left: auto;
right: -10px;
border-top-color: rgb(88, 127, 240);
border-top-color: var(--im-color-primary-light-2);
}
}

1
im-web/src/components/chat/ChatRecord.vue

@ -20,7 +20,6 @@
<el-button round type="primary" v-show="state == 'COMPLETE'" @click="onSendRecord()">立即发送</el-button>
</el-row>
</el-dialog>
</template>
<script>

43
im-web/src/components/common/Emotion.vue

@ -1,7 +1,7 @@
<template>
<div v-show="show" @click="close()">
<div class="emotion-box" :style="{ 'left': x + 'px', 'top': y + 'px' }">
<el-scrollbar style="height:250px">
<el-scrollbar style="height: 220px">
<div class="emotion-item-list">
<div class="emotion-item" v-for="(emoText, i) in $emo.emoTextList" :key="i"
@click="onClickEmo(emoText)" v-html="$emo.textToImg(emoText)">
@ -39,26 +39,23 @@
},
computed: {
x() {
return this.pos.x - 200;
return this.pos.x - 22;
},
y() {
return this.pos.y - 280;
return this.pos.y - 234;
}
}
}
</script>
<style scoped lang="scss">
.emotion-box {
position: fixed;
width: 500px;
width: 372px;
box-sizing: border-box;
padding: 5px;
border: 1px solid #53a0e79c;
border-radius: 5px;
background-color: #f5f5f5;
box-shadow: 0px 0px 10px #ccc;
//border-radius: 5px;
background-color: #fff;
box-shadow: var(--im-box-shadow);
.emotion-item-list {
display: flex;
@ -67,22 +64,22 @@
.emotion-item {
text-align: center;
cursor: pointer;
padding: 5px;
padding: 2px;
}
}
&:after {
content: "";
position: absolute;
left: 185px;
bottom: -30px;
width: 0;
height: 0;
border-style: solid dashed dashed;
border-color: #f5f5f5 transparent transparent;
overflow: hidden;
border-width: 15px;
}
//&:after {
// content: "";
// position: absolute;
// left: 185px;
// bottom: -30px;
// width: 0;
// height: 0;
// border-style: solid dashed dashed;
// border-color: #f5f5f5 transparent transparent;
// overflow: hidden;
// border-width: 15px;
//}
}
</style>

7
im-web/src/components/common/FileUpload.vue

@ -1,6 +1,6 @@
<template>
<el-upload :action="'#'" :http-request="onFileUpload" :accept="fileTypes==null?'':fileTypes.join(',')" :show-file-list="false"
:disabled="disabled" :before-upload="beforeUpload" :multiple="true">
<el-upload :action="'#'" :http-request="onFileUpload" :accept="fileTypes == null ? '' : fileTypes.join(',')"
:show-file-list="false" :disabled="disabled" :before-upload="beforeUpload" :multiple="true">
<slot></slot>
</el-upload>
</template>
@ -99,5 +99,4 @@
}
</script>
<style>
</style>
<style></style>

14
im-web/src/components/common/FullImage.vue

@ -4,7 +4,7 @@
<div class="image-box">
<img :src="url" />
</div>
<div class="close" @click="onClose">x</div>
<div class="close" @click="onClose"><i class="el-icon-close"></i></div>
</div>
</template>
@ -37,14 +37,18 @@
position: fixed;
width: 100%;
height: 100%;
left: 0;
top: 0;
bottom: 0;
right: 0;
.mask {
position: fixed;
width: 100%;
height: 100%;
background: black;
opacity: 0.9;
opacity: 0.5;
}
.image-box {
@ -69,10 +73,6 @@
color: white;
font-size: 25px;
cursor: pointer;
}
}
</style>

38
im-web/src/components/common/HeadImage.vue

@ -1,23 +1,22 @@
<template>
<div class="head-image" @click="showUserInfo($event)">
<img class="avatar-image" v-show="url" :src="url"
:style="avatarImageStyle" loading="lazy" />
<div class="head-image" @click="showUserInfo($event)" :style="{ cursor: isShowUserInfo ? 'pointer' : null }">
<img class="avatar-image" v-show="url" :src="url" :style="avatarImageStyle" loading="lazy" />
<div class="avatar-text" v-show="!url" :style="avatarTextStyle">
{{name.substring(0,2).toUpperCase()}}</div>
{{ name?.substring(0, 2).toUpperCase() }}
</div>
<div v-show="online" class="online" title="用户当前在线"></div>
<slot></slot>
</div>
</template>
<script>
export default {
name: "headImage",
data() {
return {
colors: ["#5daa31", "#c7515a", "#e03697", "#85029b",
"#c9b455", "#326eb6"]
"#c9b455", "#326eb6"
]
}
},
props: {
@ -26,7 +25,7 @@
},
size: {
type: Number,
default: 50
default: 42
},
width: {
type: Number
@ -43,15 +42,20 @@
},
name: {
type: String,
default: "?"
default: null
},
online: {
type: Boolean,
default: false
},
isShowUserInfo: {
type: Boolean,
default: true
}
},
methods: {
showUserInfo(e) {
if (!this.isShowUserInfo) return;
if (this.id && this.id > 0) {
this.$http({
url: `/user/find/${this.id}`,
@ -73,10 +77,12 @@
avatarTextStyle() {
let w = this.width ? this.width : this.size;
let h = this.height ? this.height : this.size;
return `width: ${w}px;height:${h}px;
background-color:${this.textColor};
return `
width: ${w}px;height:${h}px;
background-color: ${this.name ? this.textColor : '#fff'};
font-size:${w * 0.35}px;
border-radius: ${this.radius};`
border-radius: ${this.radius};
`
},
textColor() {
let hash = 0;
@ -92,7 +98,8 @@
<style scoped lang="scss">
.head-image {
position: relative;
cursor: pointer;
//cursor: pointer;
.avatar-image {
position: relative;
overflow: hidden;
@ -104,7 +111,8 @@
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #ccc;
//border: 1px solid #ccc;
//box-shadow: var(--im-box-shadow);
}
.online {
@ -115,7 +123,7 @@
height: 12px;
background: limegreen;
border-radius: 50%;
border: 3px solid white;
border: 2px solid white;
}
}
</style>

22
im-web/src/components/common/RightMenu.vue

@ -4,9 +4,8 @@
<el-menu text-color="#333333">
<el-menu-item v-for="(item) in items" :key="item.key" :title="item.name"
@click.native.stop="onSelectMenu(item)">
<span :class="item.icon"></span>
<!-- <span :class="item.icon"></span>-->
<span>{{ item.name }}</span>
</el-menu-item>
</el-menu>
</div>
@ -53,20 +52,23 @@
.right-menu {
position: fixed;
box-shadow: 0px 0px 10px #ccc;
border-radius: 8px;
overflow: hidden;
box-shadow: var(--im-box-shadow-light);
.el-menu {
border: 1px solid #b4b4b4;
border-radius: 7px;
border-radius: 4px;
overflow: hidden;
.el-menu-item {
height: 40px;
line-height: 40px;
border-bottom: 1px solid #d0d0d0;
height: 36px;
line-height: 36px;
min-width: 100px;
text-align: left;
padding: 0 0 0 20px;
span {
font-weight: 600;
&:hover {
background-color: var(--im-background-active);
}
}
}

22
im-web/src/components/common/UserInfo.vue

@ -3,9 +3,8 @@
<div class="user-info" :style="{ left: pos.x + 'px', top: pos.y + 'px' }" @click.stop>
<div class="user-info-box">
<div class="avatar">
<head-image :name="user.nickName" :url="user.headImageThumb" :size="70"
:online="user.online" radius="10%"
@click.native="showFullImage()"> </head-image>
<head-image :name="user.nickName" :url="user.headImageThumb" :size="70" :online="user.online"
radius="10%" @click.native="showFullImage()"> </head-image>
</div>
<div>
<el-descriptions :column="1" :title="user.userName" class="user-info-items">
@ -15,7 +14,6 @@
</el-descriptions-item>
</el-descriptions>
</div>
</div>
<el-divider content-position="center"></el-divider>
<div class="user-btn-group">
@ -100,17 +98,19 @@
<style lang="scss">
.user-info-mask {
background-color: rgba($color: #000000, $alpha: 0);
position: absolute;
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
}
.user-info {
position: absolute;
width: 300px;
background-color: white;
border: #dddddd solid 1px;
border-radius: 5px;
box-shadow: var(--im-box-shadow);
border-radius: 4px;
padding: 15px;
.user-info-box {
@ -136,6 +136,10 @@
}
}
.el-divider--horizontal {
margin: 18px 0;
}
.user-btn-group {
text-align: center;
}

30
im-web/src/components/friend/AddFriend.vue

@ -1,28 +1,28 @@
<template>
<el-dialog title="添加好友" :visible.sync="dialogVisible" width="30%" :before-close="onClose">
<el-input placeholder="输入用户名或昵称进行,最多展示20条" class="input-with-select" v-model="searchText" @keyup.enter.native="onSearch()">
<i class="el-icon-search el-input__icon" slot="suffix"
@click="onSearch()"> </i>
<el-dialog title="添加好友" :visible.sync="dialogVisible" width="400px" :before-close="onClose"
custom-class="add-friend-dialog">
<el-input placeholder="输入用户名或昵称按下enter搜索,最多展示20条" class="input-with-select" v-model="searchText" size="small"
@keyup.enter.native="onSearch()">
<i class="el-icon-search el-input__icon" slot="suffix" @click="onSearch()"> </i>
</el-input>
<el-scrollbar style="height:400px">
<div v-for="(user) in users" :key="user.id" v-show="user.id != $store.state.userStore.userInfo.id">
<div class="item">
<div class="avatar">
<head-image :name="user.nickName"
:url="user.headImage"
:online="user.online"
></head-image>
<head-image :name="user.nickName" :url="user.headImage" :online="user.online"></head-image>
</div>
<div class="add-friend-text">
<div class="text-user-name">
<div>{{ user.userName }}</div>
<div :class="user.online ? 'online-status online':'online-status'">{{ user.online?"[在线]":"[离线]"}}</div>
<div :class="user.online ? 'online-status online' : 'online-status'">{{
user.online ? "[在线]" :"[离线]"}}</div>
</div>
<div class="text-nick-name">
<div>昵称:{{ user.nickName }}</div>
</div>
</div>
<el-button type="success" size="mini" v-show="!isFriend(user.id)" @click="onAddFriend(user)">添加</el-button>
<el-button type="success" size="mini" v-show="!isFriend(user.id)"
@click="onAddFriend(user)">添加</el-button>
<el-button type="info" size="mini" v-show="isFriend(user.id)" plain disabled>已添加</el-button>
</div>
</div>
@ -53,6 +53,10 @@
this.$emit("close");
},
onSearch() {
if (!this.searchText) {
this.users = [];
return;
}
this.$http({
url: "/user/findByName",
method: "get",
@ -91,9 +95,7 @@
</script>
<style lang="scss">
.el-dialog {
min-width: 400px;
}
.add-friend-dialog {
.item {
height: 65px;
display: flex;
@ -120,6 +122,7 @@
.online-status {
font-size: 12px;
font-weight: 600;
&.online {
color: #5fb878;
}
@ -135,4 +138,5 @@
}
}
}
</style>

39
im-web/src/components/friend/FriendItem.vue

@ -1,16 +1,18 @@
<template>
<div class="friend-item" :class="active ? 'active' : ''" @contextmenu.prevent="showRightMenu($event)">
<div class="friend-avatar">
<head-image :size="45" :name="friend.nickName" :url="friend.headImage" :online="friend.online">
<head-image :size="42" :name="friend.nickName" :url="friend.headImage" :online="friend.online">
</head-image>
</div>
<div class="friend-info">
<div class="friend-name">{{ friend.nickName }}</div>
<div class="friend-online">
<el-image v-show="friend.onlineWeb" class="online" :src="require('@/assets/image/online_web.png')"
title="电脑设备在线" />
<el-image v-show="friend.onlineApp" class="online" :src="require('@/assets/image/online_app.png')"
title="移动设备在线" />
<i class="el-icon-monitor online" v-show="friend.onlineWeb" title="电脑设备在线">
<span class="online-icon"></span>
</i>
<i class="el-icon-mobile-phone online" v-show="friend.onlineApp" title="移动设备在线">
<span class="online-icon"></span>
</i>
</div>
</div>
<right-menu v-show="menu && rightMenu.show" :pos="rightMenu.pos" :items="rightMenu.items"
@ -86,28 +88,24 @@
.friend-item {
height: 50px;
display: flex;
margin-bottom: 1px;
position: relative;
padding: 5px 10px;
align-items: center;
background-color: #fafafa;
white-space: nowrap;
cursor: pointer;
&:hover {
background-color: #F8FAFF;
background-color: var(--im-background-active);
}
&.active {
background-color: #F4F9FF;
background-color: var(--im-background-active-dark);
}
.friend-avatar {
display: flex;
justify-content: center;
align-items: center;
width: 45px;
height: 45px;
}
.friend-info {
@ -118,19 +116,28 @@
text-align: left;
.friend-name {
font-size: 15px;
font-weight: 600;
line-height: 30px;
font-size: var(--im-font-size);
white-space: nowrap;
overflow: hidden;
}
.friend-online {
.online {
font-weight: bold;
padding-right: 2px;
width: 15px;
height: 15px;
font-size: 16px;
position: relative;
}
.online-icon {
position: absolute;
right: 0;
bottom: 0;
width: 6px;
height: 6px;
background: limegreen;
border-radius: 50%;
border: 1px solid white;
}
}
}

38
im-web/src/components/group/AddGroupMember.vue

@ -1,15 +1,16 @@
<template>
<el-dialog title="邀请好友" :visible.sync="visible" width="50%" :before-close="onClose">
<el-dialog title="邀请好友" :visible.sync="visible" width="620px" :before-close="onClose">
<div class="agm-container">
<div class="agm-l-box">
<el-input placeholder="搜索好友" v-model="searchText">
<div class="search">
<el-input placeholder="搜索好友" v-model="searchText" size="small">
<i class="el-icon-search el-input__icon" slot="suffix"> </i>
</el-input>
</div>
<el-scrollbar style="height:400px;">
<div v-for="(friend, index) in friends" :key="friend.id">
<friend-item v-show="friend.nickName.includes(searchText)" :showDelete="false"
@click.native="onSwitchCheck(friend)" :menu="false" :friend="friend" :index="index"
:active="false">
@click.native="onSwitchCheck(friend)" :menu="false" :friend="friend" :index="index" :active="false">
<el-checkbox :disabled="friend.disabled" @click.native.stop="" class="agm-friend-checkbox"
v-model="friend.isCheck" size="medium"></el-checkbox>
</friend-item>
@ -21,8 +22,8 @@
<div class="agm-select-tip"> 已勾选{{ checkCount }}位好友</div>
<el-scrollbar style="height:400px;">
<div v-for="(friend, index) in friends" :key="friend.id">
<friend-item v-if="friend.isCheck && !friend.disabled" :friend="friend" :index="index"
:active="false" @del="onRemoveFriend(friend,index)" :menu="false">
<friend-item v-if="friend.isCheck && !friend.disabled" :friend="friend" :index="index" :active="false"
@del="onRemoveFriend(friend, index)" :menu="false">
</friend-item>
</div>
</el-scrollbar>
@ -129,12 +130,23 @@
<style lang="scss">
.agm-container {
display: flex;
.agm-l-box {
flex: 1;
border: #587FF0 solid 1px;
border-radius: 5px;
overflow: hidden;
border: var(--im-border);
.search {
height: 40px;
display: flex;
align-items: center;
.el-input__inner {
border: unset;
border-bottom: var(--im-border);
}
}
.agm-friend-checkbox {
margin-right: 20px;
@ -144,22 +156,22 @@
.agm-arrow {
display: flex;
align-items: center;
font-size: 20px;
font-size: 18px;
padding: 10px;
font-weight: 600;
color: #687Ff0;
color: var(--im-color-primary);
}
.agm-r-box {
flex: 1;
border: #587FF0 solid 1px;
border-radius: 5px;
border: var(--im-border);
.agm-select-tip {
text-align: left;
height: 40px;
line-height: 40px;
text-indent: 5px;
text-indent: 6px;
color: var(--im-text-color-light)
}
}
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save