Browse Source

ui优化

master
libaogang 1 year ago
parent
commit
0444c71e3c
  1. 24
      im-web/package.json
  2. 97
      im-web/src/App.vue
  3. 2
      im-web/src/api/emotion.js
  4. BIN
      im-web/src/assets/image/online_app.png
  5. BIN
      im-web/src/assets/image/online_web.png
  6. 112
      im-web/src/assets/style/element.scss
  7. 43
      im-web/src/assets/style/global.css
  8. 91
      im-web/src/assets/style/im.scss
  9. 6
      im-web/src/assets/style/thems.scss
  10. 8
      im-web/src/components/chat/ChatAtBox.vue
  11. 68
      im-web/src/components/chat/ChatBox.vue
  12. 13
      im-web/src/components/chat/ChatGroupMember.vue
  13. 4
      im-web/src/components/chat/ChatGroupReaded.vue
  14. 78
      im-web/src/components/chat/ChatGroupSide.vue
  15. 9
      im-web/src/components/chat/ChatInput.vue
  16. 42
      im-web/src/components/chat/ChatItem.vue
  17. 72
      im-web/src/components/chat/ChatMessageItem.vue
  18. 43
      im-web/src/components/common/Emotion.vue
  19. 12
      im-web/src/components/common/FullImage.vue
  20. 30
      im-web/src/components/common/HeadImage.vue
  21. 22
      im-web/src/components/common/RightMenu.vue
  22. 16
      im-web/src/components/common/UserInfo.vue
  23. 13
      im-web/src/components/friend/AddFriend.vue
  24. 39
      im-web/src/components/friend/FriendItem.vue
  25. 46
      im-web/src/components/group/AddGroupMember.vue
  26. 16
      im-web/src/components/group/GroupItem.vue
  27. 3
      im-web/src/components/group/GroupMember.vue
  28. 7
      im-web/src/components/group/GroupMemberItem.vue
  29. 17
      im-web/src/components/group/GroupMemberSelector.vue
  30. 8
      im-web/src/components/rtc/RtcPrivateAcceptor.vue
  31. 68
      im-web/src/components/rtc/RtcPrivateVideo.vue
  32. 56
      im-web/src/components/setting/Setting.vue
  33. 4
      im-web/src/main.js
  34. 55
      im-web/src/view/Chat.vue
  35. 46
      im-web/src/view/Friend.vue
  36. 116
      im-web/src/view/Group.vue
  37. 212
      im-web/src/view/Home.vue

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

97
im-web/src/App.vue

@ -5,111 +5,22 @@
</template>
<script>
export default {
export default {
name: 'App',
components: {}
}
}
</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%;
color: var(--im-text-color);
font-family: var(--im-font-family);
}
.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;
}
}
</style>

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>

68
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,12 @@
<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 +667,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 +712,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 {
> 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 +756,6 @@
flex: 1;
resize: none;
font-size: 16px;
color: black;
outline: none;
text-align: left;
@ -820,15 +818,17 @@
.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;

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

@ -1,27 +1,29 @@
<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')"
<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"
<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,31 +31,32 @@
<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>
</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>
<script>
import AddGroupMember from '../group/AddGroupMember.vue';
import GroupMember from '../group/GroupMember.vue';
import AddGroupMember from '../group/AddGroupMember.vue';
import GroupMember from '../group/GroupMember.vue';
export default {
export default {
name: "chatGroupSide",
components: {
AddGroupMember,
@ -93,6 +96,7 @@
method: "put",
data: vo
}).then((group) => {
this.editing = !this.editing
this.$store.commit("updateGroup", group);
this.$emit('reload');
this.$message.success("修改成功");
@ -126,23 +130,39 @@
}
}
}
}
</script>
<style lang="scss">
.chat-group-side {
.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 +170,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 +187,7 @@
}
.invite-member-text {
font-size: 16px;
font-size: 12px;
text-align: center;
width: 100%;
height: 30px;
@ -197,10 +217,16 @@
}
}
.el-input__inner, .el-textarea__inner {
color: var(--im-text-color) !important;
}
.btn-group {
text-align: center;
margin-top: 12px;
}
}
}
}
</style>

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;
}
}

42
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">
@ -112,34 +112,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 +159,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 +185,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 +201,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 +215,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;

72
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 {
@ -261,13 +262,11 @@
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;
@ -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;
@ -444,9 +441,8 @@
.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;

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>

12
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,8 +73,6 @@
color: white;
font-size: 25px;
cursor: pointer;
}
}

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

@ -1,16 +1,15 @@
<template>
<div class="head-image" @click="showUserInfo($event)">
<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() {
@ -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);
}
}
}

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

@ -100,17 +100,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 +138,10 @@
}
}
.el-divider--horizontal {
margin: 18px 0;
}
.user-btn-group {
text-align: center;
}

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

@ -1,6 +1,6 @@
<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()">
<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>
@ -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;
@ -135,4 +137,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;
}
}
}

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

@ -1,10 +1,12 @@
<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"
@ -18,7 +20,7 @@
</div>
<div class="agm-arrow el-icon-d-arrow-right"></div>
<div class="agm-r-box">
<div class="agm-select-tip"> 已勾选{{checkCount}}位好友</div>
<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"
@ -36,9 +38,9 @@
</template>
<script>
import FriendItem from '../friend/FriendItem.vue';
import FriendItem from '../friend/FriendItem.vue';
export default {
export default {
name: "addGroupMember",
components: {
FriendItem
@ -102,7 +104,7 @@
}
},
watch: {
visible: function(newData, oldData) {
visible: function (newData, oldData) {
if (newData) {
this.friends = [];
this.$store.state.friendStore.friends.forEach((f) => {
@ -123,18 +125,29 @@
}
}
}
}
</script>
<style lang="scss">
.agm-container {
.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,23 +157,24 @@
.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)
}
}
}
</style>

16
im-web/src/components/group/GroupItem.vue

@ -1,7 +1,7 @@
<template>
<div class="group-item" :class="active ? 'active' : ''">
<div class="group-avatar">
<head-image :size="45" :name="group.showGroupName" :url="group.headImage"> </head-image>
<head-image :size="42" :name="group.showGroupName" :url="group.headImage"> </head-image>
</div>
<div class="group-name">
<div>{{group.showGroupName}}</div>
@ -36,25 +36,18 @@
.group-item {
height: 50px;
display: flex;
margin-bottom: 1px;
position: relative;
padding: 5px 10px;
align-items: center;
background-color: white;
white-space: nowrap;
cursor: pointer;
&:hover {
background-color: #F8FAFF;
background-color: var(--im-background-active);
}
&.active {
background-color: #F4F9FF;
}
.group-avatar {
width: 45px;
height: 45px;
background-color: var(--im-background-active-dark);
}
.group-name {
@ -64,8 +57,7 @@
line-height: 50px;
white-space: nowrap;
overflow: hidden;
font-size: 15px;
font-weight: 600;
font-size: var(--im-font-size);
}
}
</style>

3
im-web/src/components/group/GroupMember.vue

@ -1,12 +1,11 @@
<template>
<div class="group-member">
<head-image :id="member.userId" :name="member.showNickName"
:url="member.headImage" :size="50"
:url="member.headImage" :size="38"
:online="member.online" >
<div v-if="showDel" @click.stop="onDelete()" class="btn-kick el-icon-error"></div>
</head-image>
<div class="member-name">{{member.showNickName}}</div>
</div>
</template>

7
im-web/src/components/group/GroupMemberItem.vue

@ -40,16 +40,14 @@ export default {
<style lang="scss">
.group-member-item {
display: flex;
margin-bottom: 1px;
position: relative;
padding: 0 15px;
align-items: center;
background-color: #fafafa;
white-space: nowrap;
box-sizing: border-box;
&:hover {
background-color: #eeeeee;
background-color: var(--im-background-active);
}
&.active {
@ -63,8 +61,7 @@ export default {
text-align: left;
white-space: nowrap;
overflow: hidden;
font-size: 14px;
font-weight: 600;
font-size: var(--im-font-size);
}
}
</style>

17
im-web/src/components/group/GroupMemberSelector.vue

@ -1,5 +1,5 @@
<template>
<el-dialog title="选择成员" :visible.sync="isShow" width="50%">
<el-dialog title="选择成员" :visible.sync="isShow" width="700px">
<div class="group-member-selector">
<div class="left-box">
<el-input placeholder="搜索" v-model="searchText">
@ -118,9 +118,13 @@
.left-box {
width: 48%;
border: #587FF0 solid 1px;
border-radius: 5px;
overflow: hidden;
border: var(--im-border);
.el-input__inner {
border: none;
border-bottom: var(--im-border);
}
}
@ -130,20 +134,19 @@
font-size: 20px;
padding: 10px;
font-weight: 600;
color: #687Ff0;
color: var(--im-color-primary);
}
.right-box {
width: 48%;
border: #587FF0 solid 1px;
border-radius: 5px;
border: var(--im-border);
.select-tip {
text-align: left;
height: 40px;
line-height: 40px;
text-indent: 5px;
color: var(--im-text-color-light)
}
.checked-member-list {

8
im-web/src/components/rtc/RtcPrivateAcceptor.vue

@ -1,6 +1,6 @@
<template>
<div class="rtc-private-acceptor">
<head-image :id="friend.id" :name="friend.nickName" :url="friend.headImage" :size="100"></head-image>
<head-image :id="friend.id" :name="friend.nickName" :url="friend.headImage" :size="100" :isShowUserInfo="false"></head-image>
<div class="acceptor-text">
{{tip}}
</div>
@ -50,9 +50,9 @@
width: 250px;
height: 250px;
padding: 20px;
background-color: #eeeeee;
border: #dddddd solid 5px;
border-radius: 3%;
background-color: #fff;
box-shadow: var(--im-box-shadow-dark);
border-radius: 4px;
.acceptor-text {
padding: 10px;

68
im-web/src/components/rtc/RtcPrivateVideo.vue

@ -1,13 +1,21 @@
<template>
<div>
<el-dialog v-dialogDrag :title="title" top="5vh" :close-on-click-modal="false" :close-on-press-escape="false"
:visible.sync="showRoom" width="50%" height="70%" :before-close="onQuit">
<el-dialog
v-dialogDrag
top="5vh"
custom-class="rtc-private-video-dialog"
:title="title"
:width="width"
:visible.sync="showRoom"
:close-on-click-modal="false"
:close-on-press-escape="false"
:before-close="onQuit">
<div class="rtc-private-video">
<div v-show="isVideo" class="rtc-video-box">
<div class="rtc-video-friend" v-loading="!isChating" element-loading-text="等待对方接听..."
element-loading-background="rgba(0, 0, 0, 0.3)" >
element-loading-background="rgba(0, 0, 0, 0.1)">
<head-image class="friend-head-image" :id="friend.id" :size="80" :name="friend.nickName"
:url="friend.headImage">
:url="friend.headImage" :isShowUserInfo="false">
</head-image>
<video ref="remoteVideo" autoplay=""></video>
</div>
@ -16,10 +24,10 @@
</div>
</div>
<div v-show="!isVideo" class="rtc-voice-box" v-loading="!isChating" element-loading-text="等待对方接听..."
element-loading-background="rgba(0, 0, 0, 0.3)">
element-loading-background="rgba(0, 0, 0, 0.1)">
<head-image class="friend-head-image" :id="friend.id" :size="200" :name="friend.nickName"
:url="friend.headImage">
<div class="rtc-voice-name">{{friend.nickName}}</div>
:url="friend.headImage" :isShowUserInfo="false">
<div class="rtc-voice-name">{{ friend.nickName }}</div>
</head-image>
</div>
<div class="rtc-control-bar">
@ -28,19 +36,19 @@
</div>
</div>
</el-dialog>
<rtc-private-acceptor v-if="!isHost&&isWaiting" ref="acceptor" :friend="friend" :mode="mode" @accept="onAccept"
<rtc-private-acceptor v-if="!isHost && isWaiting" ref="acceptor" :friend="friend" :mode="mode" @accept="onAccept"
@reject="onReject"></rtc-private-acceptor>
</div>
</template>
<script>
import HeadImage from '../common/HeadImage.vue';
import RtcPrivateAcceptor from './RtcPrivateAcceptor.vue';
import ImWebRtc from '@/api/webrtc';
import ImCamera from '@/api/camera';
import RtcPrivateApi from '@/api/rtcPrivateApi'
import HeadImage from '../common/HeadImage.vue';
import RtcPrivateAcceptor from './RtcPrivateAcceptor.vue';
import ImWebRtc from '@/api/webrtc';
import ImCamera from '@/api/camera';
import RtcPrivateApi from '@/api/rtcPrivateApi'
export default {
export default {
name: 'rtcPrivateVideo',
components: {
HeadImage,
@ -123,11 +131,11 @@
this.state = "WAITING";
//
this.audio.play();
}).catch(()=>{
}).catch(() => {
this.close();
})
})
}).catch(()=>{
}).catch(() => {
//
this.close();
})
@ -223,7 +231,7 @@
this.startHeartBeat();
// 30s
this.waitTimer = setTimeout(() => {
this.API.failed(this.friend.id,"对方无应答");
this.API.failed(this.friend.id, "对方无应答");
this.$message.error("您未接听");
this.close();
}, 30000)
@ -247,7 +255,7 @@
this.API.sendCandidate(this.friend.id, candidate);
})
//
this.startChatTime();
this.startChatTime()
}
},
onRTCReject(msg) {
@ -362,6 +370,9 @@
}
},
computed: {
width() {
return this.isVideo ? '960px' : '360px'
},
title() {
let strTitle = `${this.modeText}通话-${this.friend.nickName}`;
if (this.isChating) {
@ -416,11 +427,11 @@
beforeUnmount() {
this.onQuit();
}
}
}
</script>
<style lang="scss">
.rtc-private-video {
.rtc-private-video {
position: relative;
.el-loading-text {
@ -434,7 +445,6 @@
.rtc-video-box {
position: relative;
border: #4880b9 solid 1px;
background-color: #eeeeee;
.rtc-video-friend {
@ -457,9 +467,7 @@
z-index: 99999;
width: 25vh;
right: 0;
bottom: 0;
box-shadow: 0px 0px 5px #ccc;
background-color: #cccccc;
bottom: -1px;
video {
width: 100%;
@ -473,15 +481,14 @@
position: relative;
display: flex;
justify-content: center;
border: #4880b9 solid 1px;
align-items: center;
width: 100%;
height: 50vh;
padding-top: 10vh;
background-color: aliceblue;
height: 300px;
background-color: var(--im-color-primary-light-9);
.rtc-voice-name {
text-align: center;
font-size: 22px;
font-size: 20px;
font-weight: 600;
}
}
@ -496,5 +503,6 @@
cursor: pointer;
}
}
}
}
</style>

56
im-web/src/components/setting/Setting.vue

@ -1,7 +1,7 @@
<template>
<el-dialog class="setting" title="设置" :visible.sync="visible" width="500px" :before-close="onClose">
<el-form :model="userInfo" label-width="70px" :rules="rules" ref="settingForm">
<el-form-item label="头像">
<el-dialog class="setting" title="设置" :visible.sync="visible" width="420px" :before-close="onClose">
<el-form :model="userInfo" label-width="80px" :rules="rules" ref="settingForm" size="small">
<el-form-item label="头像" style="margin-bottom: 0 !important;">
<file-upload class="avatar-uploader"
:action="imageAction"
:showLoading="true"
@ -13,10 +13,10 @@
</file-upload>
</el-form-item>
<el-form-item label="用户名">
<el-input disabled v-model="userInfo.userName" autocomplete="off"></el-input>
<el-input disabled v-model="userInfo.userName" autocomplete="off" size="small"></el-input>
</el-form-item>
<el-form-item prop="nickName" label="昵称">
<el-input v-model="userInfo.nickName" autocomplete="off"></el-input>
<el-input v-model="userInfo.nickName" autocomplete="off" size="small"></el-input>
</el-form-item>
<el-form-item label="性别">
<el-radio-group v-model="userInfo.sex">
@ -25,7 +25,7 @@
</el-radio-group>
</el-form-item>
<el-form-item label="个性签名">
<el-input type="textarea" v-model="userInfo.signature"></el-input>
<el-input type="textarea" v-model="userInfo.signature" :rows="3"></el-input>
</el-form-item>
</el-form>
@ -37,19 +37,17 @@
</template>
<script>
import FileUpload from "../common/FileUpload.vue";
import FileUpload from "../common/FileUpload.vue";
export default {
export default {
name: "setting",
components: {
FileUpload
},
data() {
return {
userInfo: {
},
maxSize: 5*1024*1024,
userInfo: {},
maxSize: 5 * 1024 * 1024,
action: "/image/upload",
rules: {
nickName: [{
@ -74,8 +72,8 @@
url: "/user/update",
method: "put",
data: this.userInfo
}).then(()=>{
this.$store.commit("setUserInfo",this.userInfo);
}).then(() => {
this.$store.commit("setUserInfo", this.userInfo);
this.$emit("close");
this.$message.success("修改成功");
})
@ -91,27 +89,29 @@
type: Boolean
}
},
computed:{
imageAction(){
computed: {
imageAction() {
return `/image/upload`;
}
},
watch: {
visible: function(newData, oldData) {
visible: function (newData, oldData) {
//
let mine = this.$store.state.userStore.userInfo;
this.userInfo = JSON.parse(JSON.stringify(mine));
}
}
}
}
</script>
<style lang="scss" >
.setting {
<style lang="scss">
.setting {
.el-form {
padding: 30px;
padding: 10px 0 0 10px;
}
.avatar-uploader {
--width: 112px;
.el-upload {
border: 1px dashed #d9d9d9 !important;
@ -126,19 +126,19 @@
}
.avatar-uploader-icon {
font-size: 28px;
font-size: 24px;
color: #8c939d;
width: 178px;
height: 178px;
line-height: 178px;
width: var(--width);
height: var(--width);
line-height: var(--width);
text-align: center;
}
.avatar {
width: 178px;
height: 178px;
width: var(--width);
height: var(--width);
display: block;
}
}
}
}
</style>

4
im-web/src/main.js

@ -2,8 +2,9 @@ import Vue from 'vue'
import App from './App'
import router from './router'
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import './assets/style/im.scss';
import './assets/iconfont/iconfont.css';
import httpRequest from './api/httpRequest';
import * as socketApi from './api/wssocket';
import * as messageType from './api/messageType';
@ -33,3 +34,4 @@ new Vue({
store,
render: h=>h(App)
})

55
im-web/src/view/Chat.vue

@ -1,16 +1,15 @@
<template>
<el-container class="chat-page">
<el-aside width="280px" class="chat-list-box">
<el-aside width="260px" class="chat-list-box">
<div class="chat-list-header">
<el-input class="search-text" placeholder="搜索" v-model="searchText">
<el-input class="search-text" size="small" placeholder="搜索" v-model="searchText">
<i class="el-icon-search el-input__icon" slot="prefix"> </i>
</el-input>
</div>
<div class="chat-list-loading" v-if="loading" v-loading="true" element-loading-text="消息接收中..."
element-loading-spinner="el-icon-loading" element-loading-background="#eee">
<div class="chat-loading-box"></div>
element-loading-spinner="el-icon-loading" element-loading-background="#F9F9F9" element-loading-size="24">
</div>
<el-scrollbar class="chat-list-items">
<el-scrollbar class="chat-list-items" v-else>
<div v-for="(chat,index) in chatStore.chats" :key="index">
<chat-item v-show="!chat.delete&&chat.showName.includes(searchText)" :chat="chat" :index="index"
@click.native="onActiveItem(index)" @delete="onDelItem(index)" @top="onTop(index)"
@ -25,10 +24,10 @@
</template>
<script>
import ChatItem from "../components/chat/ChatItem.vue";
import ChatBox from "../components/chat/ChatBox.vue";
import ChatItem from "../components/chat/ChatItem.vue";
import ChatBox from "../components/chat/ChatBox.vue";
export default {
export default {
name: "chat",
components: {
ChatItem,
@ -57,48 +56,48 @@
chatStore() {
return this.$store.state.chatStore;
},
loading(){
loading() {
return this.chatStore.loadingGroupMsg || this.chatStore.loadingPrivateMsg
}
}
}
}
</script>
<style lang="scss">
.chat-page {
.chat-page {
.chat-list-box {
display: flex;
flex-direction: column;
border-right: #53a0e79c solid 1px;
background: white;
width: 3rem;
background: var(--im-background);
.chat-list-header {
padding: 3px 8px;
line-height: 50px;
border-bottom: 1px #ddd solid;
.el-input__inner {
border-radius: 10px !important;
background-color: #F8F8F8;
}
height: 50px;
display: flex;
align-items: center;
padding: 0 8px;
}
.chat-list-loading{
.chat-list-loading {
height: 50px;
background-color: #eee;
.chat-loading-box{
.el-icon-loading {
font-size: 24px;
color: var(--im-text-color-light);
}
.el-loading-text {
color: var(--im-text-color-light);
}
.chat-loading-box {
height: 100%;
}
}
.chat-list-items {
flex: 1;
background: #F8F8F8;
margin: 0 3px;
}
}
}
}
</style>

46
im-web/src/view/Friend.vue

@ -1,8 +1,8 @@
<template>
<el-container class="friend-page">
<el-aside width="280px" class="friend-list-box">
<el-aside width="260px" class="friend-list-box">
<div class="friend-list-header">
<el-input class="search-text" placeholder="搜索" v-model="searchText">
<el-input class="search-text" size="small" placeholder="搜索" v-model="searchText">
<i class="el-icon-search el-input__icon" slot="prefix"> </i>
</el-input>
<el-button plain class="add-btn" icon="el-icon-plus" title="添加好友"
@ -24,8 +24,8 @@
</div>
<div v-show="userInfo.id">
<div class="friend-detail">
<head-image :size="200" :name="userInfo.nickName" :url="userInfo.headImage"
@click.native="showFullImage()" radius="10%"></head-image>
<head-image :size="120" :name="userInfo.nickName" :url="userInfo.headImage"
@click.native="showFullImage()"></head-image>
<div>
<div class="info-item">
<el-descriptions title="好友信息" class="description" :column="1">
@ -36,7 +36,6 @@
<el-descriptions-item label="性别">{{ userInfo.sex==0?"男":"女" }}</el-descriptions-item>
<el-descriptions-item label="签名">{{ userInfo.signature }}</el-descriptions-item>
</el-descriptions>
</div>
<div class="frient-btn-group">
<el-button v-show="isFriend" icon="el-icon-position" type="primary"
@ -48,7 +47,7 @@
</div>
</div>
</div>
<el-divider content-position="center"></el-divider>
<!-- <el-divider content-position="center"></el-divider>-->
</div>
</el-container>
@ -182,36 +181,24 @@
.friend-list-box {
display: flex;
flex-direction: column;
border-right: #53a0e79c solid 1px;
background: white;
background: var(--im-background);
.friend-list-header {
height: 50px;
display: flex;
align-items: center;
padding: 3px 8px;
border-bottom: 1px #ddd solid;
.el-input__inner {
border-radius: 10px !important;
background-color: #F8F8F8;
}
padding: 0 8px;
.add-btn {
padding: 5px !important;
margin: 5px;
font-size: 20px;
color: #587FF0;
border: #587FF0 1px solid;
background-color: #F0F8FF;
font-size: 16px;
border-radius: 50%;
}
}
.friend-list-items {
flex: 1;
margin: 0 3px;
background: #F8F8F8;
}
}
@ -220,14 +207,14 @@
flex-direction: column;
.friend-header {
padding: 3px;
height: 50px;
line-height: 50px;
font-size: 20px;
font-weight: 600;
text-align: center;
background-color: white;
border-bottom: 1px #ddd solid;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 12px;
font-size: var(--im-font-size-larger);
border-bottom: var(--im-border);
box-sizing: border-box;
}
.friend-detail {
@ -239,12 +226,11 @@
.info-item {
margin-left: 20px;
background-color: #ffffff;
border-radius: 10px ;
border: 1px #ddd solid;
}
.description {
padding: 20px 20px 0px 20px;
padding: 20px 20px 0 20px;
}
}

116
im-web/src/view/Group.vue

@ -1,8 +1,8 @@
<template>
<el-container class="group-page">
<el-aside width="280px" class="group-list-box">
<el-aside width="260px" class="group-list-box">
<div class="group-list-header">
<el-input class="search-text" placeholder="搜索" v-model="searchText">
<el-input class="search-text" size="small" placeholder="搜索" v-model="searchText">
<i class="el-icon-search el-input__icon" slot="prefix"> </i>
</el-input>
<el-button plain class="add-btn" icon="el-icon-plus" title="创建群聊" @click="onCreateGroup()"></el-button>
@ -17,9 +17,9 @@
</el-aside>
<el-container class="group-box">
<div class="group-header" v-show="activeGroup.id">
{{activeGroup.showGroupName}}({{groupMembers.length}})
{{ activeGroup.showGroupName }}({{ groupMembers.length }})
</div>
<el-scrollbar class="group-container">
<div class="group-container">
<div v-show="activeGroup.id">
<div class="group-info">
<div>
@ -29,13 +29,14 @@
<img v-if="activeGroup.headImage" :src="activeGroup.headImage" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</file-upload>
<head-image v-show="!isOwner" class="avatar" :size="200" :url="activeGroup.headImage"
radius="10%" :name="activeGroup.showGroupName">
<head-image v-show="!isOwner" class="avatar" :size="120" :url="activeGroup.headImage"
:name="activeGroup.showGroupName">
</head-image>
<el-button class="send-btn" icon="el-icon-position" type="primary"
@click="onSendMessage()">发消息</el-button>
@click="onSendMessage()">发消息
</el-button>
</div>
<el-form class="group-form" label-width="130px" :model="activeGroup" :rules="rules"
<el-form class="group-form" label-width="130px" :model="activeGroup" :rules="rules" size="small"
ref="groupForm">
<el-form-item label="群聊名称" prop="name">
<el-input v-model="activeGroup.name" :disabled="!isOwner" maxlength="20"></el-input>
@ -52,22 +53,22 @@
:placeholder="$store.state.userStore.userInfo.nickName"></el-input>
</el-form-item>
<el-form-item label="群公告">
<el-input v-model="activeGroup.notice" :disabled="!isOwner" type="textarea"
<el-input v-model="activeGroup.notice" :disabled="!isOwner" type="textarea" :rows="3"
maxlength="1024" placeholder="群主未设置"></el-input>
</el-form-item>
<div>
<el-button type="warning" v-show="isOwner" @click="onInviteMember()">邀请</el-button>
<el-button type="success" @click="onSaveGroup()">保存</el-button>
<el-button type="danger" v-show="!isOwner" @click="onQuit()">退出群聊</el-button>
<el-button type="danger" v-show="isOwner" @click="onDissolve()">解散群聊</el-button>
<el-button type="danger" v-show="!isOwner" @click="onQuit()">退出</el-button>
<el-button type="danger" v-show="isOwner" @click="onDissolve()">解散</el-button>
</div>
</el-form>
</div>
<el-divider content-position="center"></el-divider>
<el-scrollbar style="height:200px;">
<div class="group-member-list">
<div v-for="(member) in groupMembers" :key="member.id">
<group-member v-show="!member.quit" class="group-member" :member="member"
:showDel="isOwner&&member.userId!=activeGroup.ownerId" @del="onKick"></group-member>
:showDel="isOwner && member.userId!=activeGroup.ownerId" @del="onKick"></group-member>
</div>
<div class="group-invite">
<div class="invite-member-btn" title="邀请好友进群聊" @click="onInviteMember()">
@ -79,21 +80,21 @@
@close="onCloseAddGroupMember"></add-group-member>
</div>
</div>
</el-scrollbar>
</div>
</el-scrollbar>
</div>
</el-container>
</el-container>
</template>
<script>
import GroupItem from '../components/group/GroupItem';
import FileUpload from '../components/common/FileUpload';
import GroupMember from '../components/group/GroupMember.vue';
import AddGroupMember from '../components/group/AddGroupMember.vue';
import HeadImage from '../components/common/HeadImage.vue';
export default {
import GroupItem from '../components/group/GroupItem';
import FileUpload from '../components/common/FileUpload';
import GroupMember from '../components/group/GroupMember.vue';
import AddGroupMember from '../components/group/AddGroupMember.vue';
import HeadImage from '../components/common/HeadImage.vue';
export default {
name: "group",
components: {
GroupItem,
@ -265,44 +266,32 @@
return `/image/upload`;
}
}
}
}
</script>
<style lang="scss">
.group-page {
.group-page {
.group-list-box {
display: flex;
flex-direction: column;
border-right: #53a0e79c solid 1px;
background: #F8F8F8;
background: var(--im-background);
.group-list-header {
height: 50px;
display: flex;
align-items: center;
padding: 3px 8px;
background-color: white;
border-bottom: 1px #ddd solid;
.el-input__inner {
border-radius: 10px !important;
}
padding: 0 8px;
.add-btn {
padding: 5px !important;
margin: 5px;
font-size: 20px;
color: #587FF0;
border: #587FF0 1px solid;
background-color: #F0F8FF;
font-size: 16px;
border-radius: 50%;
}
}
.group-list-items {
flex: 1;
margin: 0 3px;
background: #F8F8F8;
}
}
@ -311,17 +300,20 @@
flex-direction: column;
.group-header {
padding: 3px;
height: 50px;
display: flex;
justify-content: space-between;
padding: 0 12px;
line-height: 50px;
font-size: 20px;
font-weight: 600;
text-align: center;
background-color: white;
border-bottom: 1px #ddd solid;
font-size: var(--im-font-size-larger);
border-bottom: var(--im-border);
}
.el-divider--horizontal {
margin: 16px 0;
}
.group-container {
overflow: auto;
padding: 20px;
flex: 1;
@ -336,6 +328,7 @@
}
.avatar-uploader {
--width: 120px;
text-align: left;
.el-upload {
@ -353,34 +346,33 @@
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 200px;
height: 200px;
line-height: 200px;
width: var(--width);
height: var(--width);
line-height: var(--width);
text-align: center;
}
.avatar {
width: 200px;
height: 200px;
width: var(--width);
height: var(--width);
display: block;
}
}
.send-btn {
margin-top: 20px;
margin-top: 12px;
}
}
.group-member-list {
padding: 5px 20px;
padding: 0 12px;
display: flex;
align-items: center;
flex-wrap: wrap;
font-size: 16px;
text-align: center;
.group-member {
margin-right: 15px;
margin-right: 5px;
}
.group-invite {
@ -390,11 +382,11 @@
width: 60px;
.invite-member-btn {
width: 100%;
height: 60px;
line-height: 60px;
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;
@ -404,7 +396,7 @@
}
.invite-member-text {
font-size: 16px;
font-size: var(--im-font-size-smaller);
text-align: center;
width: 100%;
height: 30px;
@ -417,6 +409,8 @@
}
}
}
}
}
</style>

212
im-web/src/view/Home.vue

@ -1,40 +1,53 @@
<template>
<el-container class="home-page">
<el-aside width="80px" class="navi-bar">
<div class="home-page">
<div class="app-container" :class="{fullscreen: isFullscreen}">
<div class="navi-bar">
<div class="navi-bar-box">
<div class="top">
<div class="user-head-image">
<head-image :name="$store.state.userStore.userInfo.nickName"
:url="$store.state.userStore.userInfo.headImageThumb" :size="60"
:size="38"
:url="$store.state.userStore.userInfo.headImageThumb"
@click.native="showSettingDialog = true">
</head-image>
</div>
<el-menu background-color="#E8F2FF" style="margin-top: 25px;">
<el-menu-item title="聊天">
<div class="menu">
<router-link class="link" v-bind:to="'/home/chat'">
<div class="menu-item">
<span class="icon iconfont icon-chat"></span>
<div v-show="unreadCount > 0" class="unread-text">{{ unreadCount }}</div>
</div>
</router-link>
</el-menu-item>
<el-menu-item title="好友">
<router-link class="link" v-bind:to="'/home/friend'">
<div class="menu-item">
<span class="icon iconfont icon-friend"></span>
</div>
</router-link>
</el-menu-item>
<el-menu-item title="群聊">
<router-link class="link" v-bind:to="'/home/group'">
<span class="icon iconfont icon-group"></span>
<div class="menu-item">
<span class="icon iconfont icon-group" style="font-size: 28px"></span>
</div>
</router-link>
</el-menu-item>
<el-menu-item title="设置" @click="showSetting()">
<span class="icon iconfont icon-setting"></span>
</el-menu-item>
</el-menu>
<div class="exit-box" @click="onExit()" title="退出">
</div>
</div>
<div class="botoom">
<div class="botoom-item" @click="isFullscreen = !isFullscreen">
<i class="el-icon-full-screen"></i>
</div>
<div class="botoom-item" @click="showSetting">
<span class="icon iconfont icon-setting" style="font-size: 20px"></span>
</div>
<div class="botoom-item" @click="onExit()" title="退出">
<span class="icon iconfont icon-exit"></span>
</div>
</el-aside>
<el-main class="content-box">
</div>
</div>
</div>
<div class="content-box">
<router-view></router-view>
</el-main>
</div>
<setting :visible="showSettingDialog" @close="closeSetting()"></setting>
<user-info v-show="uiStore.userInfo.show" :pos="uiStore.userInfo.pos" :user="uiStore.userInfo.user"
@close="$store.commit('closeUserInfoBox')"></user-info>
@ -42,19 +55,20 @@
@close="$store.commit('closeFullImageBox')"></full-image>
<rtc-private-video ref="rtcPrivateVideo"></rtc-private-video>
<rtc-group-video ref="rtcGroupVideo"></rtc-group-video>
</el-container>
</div>
</div>
</template>
<script>
import HeadImage from '../components/common/HeadImage.vue';
import Setting from '../components/setting/Setting.vue';
import UserInfo from '../components/common/UserInfo.vue';
import FullImage from '../components/common/FullImage.vue';
import RtcPrivateVideo from '../components/rtc/RtcPrivateVideo.vue';
import RtcPrivateAcceptor from '../components/rtc/RtcPrivateAcceptor.vue';
import RtcGroupVideo from '../components/rtc/RtcGroupVideo.vue';
import HeadImage from '../components/common/HeadImage.vue';
import Setting from '../components/setting/Setting.vue';
import UserInfo from '../components/common/UserInfo.vue';
import FullImage from '../components/common/FullImage.vue';
import RtcPrivateVideo from '../components/rtc/RtcPrivateVideo.vue';
import RtcPrivateAcceptor from '../components/rtc/RtcPrivateAcceptor.vue';
import RtcGroupVideo from '../components/rtc/RtcGroupVideo.vue';
export default {
export default {
components: {
HeadImage,
Setting,
@ -67,7 +81,8 @@
data() {
return {
showSettingDialog: false,
lastPlayAudioTime: new Date().getTime() - 1000
lastPlayAudioTime: new Date().getTime() - 1000,
isFullscreen: false
}
},
methods: {
@ -107,7 +122,7 @@
} else if (cmd == 4) {
//
this.handleGroupMessage(msgInfo);
} else if (cmd == 5){
} else if (cmd == 5) {
//
this.handleSystemMessage(msgInfo);
}
@ -178,6 +193,7 @@
})
},
insertPrivateMessage(friend, msg) {
let chatInfo = {
type: 'PRIVATE',
targetId: friend.id,
@ -237,6 +253,7 @@
})
},
insertGroupMessage(group, msg) {
let chatInfo = {
type: 'GROUP',
targetId: group.id,
@ -253,11 +270,12 @@
this.playAudioTip();
}
},
handleSystemMessage(msg){
handleSystemMessage(msg) {
//
if (msg.type == this.$enums.MESSAGE_TYPE.USER_BANNED) {
this.$wsApi.close(3000);
this.$alert("您的账号已被管理员封禁,原因:"+ msg.content, "账号被封禁", {
this.$alert("您的账号已被管理员封禁,原因:" + msg.content, "账号被封禁", {
confirmButtonText: '确定',
callback: action => {
this.onExit();
@ -273,7 +291,7 @@
},
playAudioTip() {
// 线
if(this.$store.getters.isLoading()){
if (this.$store.getters.isLoading()) {
return;
}
//
@ -284,6 +302,7 @@
audio.src = url;
audio.play();
}
},
showSetting() {
this.showSettingDialog = true;
@ -332,7 +351,7 @@
let unreadCount = 0;
let chats = this.$store.state.chatStore.chats;
chats.forEach((chat) => {
if(!chat.delete){
if (!chat.delete) {
unreadCount += chat.unreadCount
}
});
@ -354,50 +373,108 @@
unmounted() {
this.$wsApi.close();
}
}
}
</script>
<style scoped lang="scss">
.home-page {
height: 100vh;
width: 100vw;
display: flex;
justify-content: center;
align-items: center;
border-radius: 4px;
overflow: hidden;
background: #e8f2ff;
//background-image: url('../assets/image/background.jpg');
.app-container {
width: 62vw;
height: 80vh;
display: flex;
min-height: 600px;
min-width: 970px;
position: absolute;
border-radius: 4px;
overflow: hidden;
box-shadow: var(--im-box-shadow-dark);
transition: 0.2s;
&.fullscreen {
transition: 0.2s;
width: 100vw;
height: 100vh;
}
}
.navi-bar {
background: #E8F2FF;
padding: 10px;
--icon-font-size: 22px;
--width: 56px;
width: var(--width);
background: var(--im-color-primary);
padding-top: 20px;
border-right: #53a0e79c solid 1px;
.el-menu {
border: none;
flex: 1;
.navi-bar-box {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
.el-menu-item {
margin: 25px 0;
background-color: #E8F2FF !important;
padding: 0 !important;
text-align: center;
.botoom {
margin-bottom: 30px;
}
}
.user-head-image {
display: flex;
justify-content: center;
}
--menu-color: #9DC4FF;
.menu {
height: 200px;
//margin-top: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
.link {
text-decoration: none;
}
&.router-link-active .icon {
color: #195ee2;
font-size: 28px;
.router-link-active .menu-item {
color: #fff;
background: var(--im-color-primary-light-2);
}
.link:not(.router-link-active) .menu-item:hover {
color: #fff;
}
.menu-item {
position: relative;
color: var(--menu-color);
width: var(--width);
height: 46px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 12px;
.icon {
font-size: 26px;
color: #666;
font-size: var(--icon-font-size)
}
.unread-text {
position: absolute;
line-height: 20px;
background-color: #f56c6c;
left: 36px;
top: 7px;
background-color: var(--im-color-danger);
left: 28px;
top: 8px;
color: white;
border-radius: 30px;
padding: 0 5px;
font-size: 10px;
font-size: 12px;
text-align: center;
white-space: nowrap;
border: 1px solid #f1e5e5;
@ -405,27 +482,34 @@
}
}
.exit-box {
position: absolute;
width: 60px;
bottom: 40px;
text-align: center;
.botoom-item {
display: flex;
justify-content: center;
align-items: center;
height: 50px;
width: 100%;
cursor: pointer;
color: var(--menu-color);
font-size: var(--icon-font-size);
.icon {
font-size: 28px;
font-size: var(--icon-font-size)
}
&:hover {
font-weight: 600;
color: #fff;
}
}
}
.content-box {
flex: 1;
padding: 0;
background-color: #f8f8f8;
color: black;
background-color: #fff;
text-align: center;
}
}
</style>
Loading…
Cancel
Save