Browse Source

!166 3.9.

Merge pull request !166 from blue/v_3.0.0
master
blue 6 months ago
committed by Gitee
parent
commit
0ef360cb0b
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 11
      README.md
  2. 1
      im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java
  3. 5
      im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java
  4. 8
      im-uniapp/App.vue
  5. 7
      im-uniapp/common/wssocket.js
  6. 3
      im-uniapp/components/nav-bar/nav-bar.vue
  7. 8
      im-uniapp/im-var.scss
  8. 33
      im-uniapp/im.scss
  9. 48
      im-uniapp/pages/chat/chat.vue
  10. 51
      im-uniapp/pages/friend/friend.vue
  11. 56
      im-uniapp/pages/group/group.vue
  12. 282
      im-uniapp/pages/login/login.vue
  13. 341
      im-uniapp/pages/register/register.vue
  14. 102
      im-uniapp/static/icon/iconfont.css
  15. BIN
      im-uniapp/static/icon/iconfont.ttf
  16. 9
      im-uniapp/store/chatStore.js
  17. 26
      im-web/src/assets/style/element.scss
  18. 12
      im-web/src/assets/style/im.scss
  19. 2
      im-web/src/components/chat/ChatBox.vue
  20. 22
      im-web/src/components/chat/ChatItem.vue
  21. 20
      im-web/src/components/chat/ChatMessageItem.vue
  22. 19
      im-web/src/components/common/FullImage.vue
  23. 131
      im-web/src/components/common/ResizableAside.vue
  24. 2
      im-web/src/components/friend/AddFriend.vue
  25. 4
      im-web/src/components/friend/FriendItem.vue
  26. 6
      im-web/src/components/group/GroupItem.vue
  27. 4
      im-web/src/components/group/GroupMemberItem.vue
  28. 9
      im-web/src/store/chatStore.js
  29. 4
      im-web/src/store/configStore.js
  30. 14
      im-web/src/view/Chat.vue
  31. 14
      im-web/src/view/Friend.vue
  32. 13
      im-web/src/view/Group.vue
  33. 29
      im-web/src/view/Home.vue

11
README.md

@ -7,7 +7,7 @@
1. 盒子IM是一个仿微信实现的网页版聊天软件,不依赖任何第三方收费组件。
1. 支持私聊、群聊、离线消息、发送语音、图片、文件、已读未读、群@等功能
1. 支持单人、多人音视频通话(基于原生webrtc实现,需要ssl证书)
1. 支持音视频通话(基于原生webrtc实现,需要ssl证书)
1. uniapp端兼容app、h5、微信小程序,可与web端同时在线,并保持消息同步
1. 后端采用springboot+netty实现,网页端使用vue,移动端使用uniapp
1. 服务器支持集群化部署,具有良好的横向扩展能力
@ -28,7 +28,7 @@
网页端:https://www.boxim.online
移动安卓端:https://www.boxim.online/download/boxim.apk
移动安卓端:已上架至各大主流手机应用市场以及腾讯应用宝,搜索"盒子IM",下载安装即可
移动ios端: 已上架至app store,搜索"盒子IM",下载安装即可
@ -40,8 +40,9 @@
说明:
1.**请勿利用测试账号辱骂他人、发布低俗内容,否则将直接对您的IP进行封禁**
2.由于微信小程序每次发布审核过于严苛和繁琐,暂时不再提供体验环境,但uniapp端依然会继续兼容小程序
3.体验环境部署的是商业版本,与开源版本功能存在一定差异,具体请参考:
2.由于部分厂商上架审核要求实名制,app端隐藏了"用户名注册"注册通道,可通过长按注册页面蓝色文字标题解除限制
3.由于微信小程序每次发布审核过于严苛和繁琐,暂时不再提供体验环境,但uniapp端依然会继续兼容小程序
4.体验环境部署的是商业版本,与开源版本功能存在一定差异,具体请参考:
https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29#SbvXq
@ -109,7 +110,7 @@ https://www.yuque.com/u1475064/mufu2a/vn5u10ephxh9sau8
群聊:
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E7%BE%A4%E8%81%8A.jpg)
群通话:
群通话(商业版)
![输入图片说明](%E6%88%AA%E5%9B%BE/web/%E5%A4%9A%E4%BA%BA%E9%80%9A%E8%AF%9D.jpg)
好友列表:

1
im-platform/src/main/java/com/bx/implatform/dto/RegisterDTO.java

@ -20,7 +20,6 @@ public class RegisterDTO {
private String password;
@Length(max = 20, message = "昵称不能大于20字符")
@NotEmpty(message = "用户昵称不可为空")
@Schema(description = "用户昵称")
private String nickName;

5
im-platform/src/main/java/com/bx/implatform/service/impl/UserServiceImpl.java

@ -1,5 +1,6 @@
package com.bx.implatform.service.impl;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
@ -109,6 +110,10 @@ public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements Us
@Override
public void register(RegisterDTO dto) {
// 昵称默认跟用户名保持一致
if(StrUtil.isEmpty(dto.getNickName())){
dto.setUserName(dto.getUserName());
}
User user = this.findUserByUserName(dto.getUserName());
if(!dto.getUserName().equals(sensitiveFilterUtil.filter(dto.getUserName()))){
throw new GlobalException("用户名包含敏感字符");

8
im-uniapp/App.vue

@ -74,8 +74,10 @@ export default {
wsApi.onClose((res) => {
console.log("ws断开", res);
//
if (!this.reconnecting) {
this.reconnectWs();
this.configStore.setAppInit(false);
}
})
},
loadStore() {
@ -496,7 +498,7 @@ uni-page-head {
// #endif
page {
background-color: $im-bg;
background: $im-bg-linear;
}
.tab-page {
@ -519,7 +521,7 @@ page {
// #endif
color: $im-text-color;
background-color: $im-bg;
background: $im-bg-linear;
font-size: $im-font-size;
font-family: $font-family;
}
@ -542,7 +544,7 @@ page {
// #endif
color: $im-text-color;
background-color: $im-bg;
background: $im-bg-linear;
font-size: $im-font-size;
font-family: $font-family;
}

7
im-uniapp/common/wssocket.js

@ -66,9 +66,7 @@ let connect = (wsurl, token) => {
socketTask.onError((e) => {
console.log("ws错误:",e)
close();
isConnect = false;
closeCallBack && closeCallBack({ code: 1006 });
close(1006);
})
}
@ -97,6 +95,9 @@ let close = (code) => {
complete: (res) => {
console.log("关闭websocket连接");
isConnect = false;
if (code != 3099) {
closeCallBack && closeCallBack(res);s
}
},
fail: (e) => {
console.log("关闭websocket连接失败", e);

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

@ -73,8 +73,7 @@ export default {
<style scoped lang="scss">
.im-nav-bar {
background-color: #fff;
//background-color: $im-bg;
background: $im-bg-linear;
position: fixed;
top: 0;
width: 100%;

8
im-uniapp/im-var.scss

@ -47,8 +47,12 @@ $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-bg: #f8f9ff;
$im-bg-linear: linear-gradient(135deg, #f8f9ff 0%, #ffffff 100%);
$im-bg-active: #f7f8fc;
$im-bg-active-dark: $im-color-primary-light-9;
$im-bg-input: #fafafa;
// 标题
$im-title-size: 26px;

33
im-uniapp/im.scss

@ -1,10 +1,11 @@
/** 原生button样式 **/
uni-button {
font-size: $im-font-size !important;
border-radius: 40rpx !important;
}
uni-button[type='primary'] {
color: #fff !important;
background-color: $im-color-primary !important;
background: linear-gradient(135deg, $im-color-primary, lighten($im-color-primary, 15%)) !important;
}
uni-button[type='primary'][plain] {
@ -15,13 +16,13 @@ uni-button[type='primary'][plain] {
uni-button[type='warn'] {
color: #fff !important;
background-color: $im-color-danger !important;
background: linear-gradient(135deg, $im-color-danger, lighten($im-color-danger, 15%)) !important;
}
uni-button[type='warn'][plain] {
color: $im-color-danger !important;
border: 1px solid $im-color-danger !important;
background-color: transparent !important;
background: transparent !important;
}
uni-button[size='mini'] {
@ -36,24 +37,24 @@ button {
button[type='primary'] {
color: #fff !important;
background-color: $im-color-primary !important;
background: linear-gradient(135deg, $im-color-primary, lighten($im-color-primary, 15%)) !important;
}
button[type='primary'][plain] {
color: $im-color-primary !important;
border: 1px solid $im-color-primary;
background-color: transparent;
border: 2px solid $im-color-primary;
background: transparent;
}
button[type='warn'] {
color: #fff !important;
background-color: $im-color-danger !important;
background: linear-gradient(135deg, $im-color-danger, lighten($im-color-danger, 15%)) !important;
}
button[type='warn'][plain] {
color: $im-color-danger !important;
border: 1px solid $im-color-danger !important;
background-color: transparent !important;
background: transparent !important;
}
button[size='mini'] {
@ -156,6 +157,7 @@ button[size='mini'] {
.uni-input-input:disabled {
color: $im-text-color-light;
}
.uni-forms-item.is-direction-top .uni-forms-item__label {
padding: 0 !important;
}
@ -166,10 +168,15 @@ button[size='mini'] {
color: unset !important;
padding: 10px 0 !important;
}
.uni-tag-text--small{
font-size: 10px !important;
font-weight: bolder !important;
.uni-tag{
transform: scale(0.9);
margin-left: 5rpx;
border-radius: 12rpx !important;
font-weight: 600 !important;
white-space: nowrap;
}
.uni-switch-input-checked {
background-color: $im-color-primary-light-1 !important;
border-color: $im-color-primary-light-1 !important;
@ -191,13 +198,13 @@ button[size='mini'] {
.nav-bar {
height: 100rpx;
padding: 0 20rpx;
display: flex;
align-items: center;
background-color: white;
border-bottom: 1px solid $im-border;
border-bottom: 1rpx solid $im-border;
.nav-search {
flex: 1;

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

@ -18,7 +18,11 @@
</view>
</view>
<view class="chat-tip" v-if="!initializing && !loading && chatStore.chats.length == 0">
温馨提示您现在还没有任何聊天消息快跟您的好友发起聊天吧~
<view class="tip-icon">
<text class="iconfont icon-chat"></text>
</view>
<view class="tip-title">还没有聊天</view>
<view class="tip-content">添加好友或创建群聊开始精彩的对话吧</view>
</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">
@ -140,12 +144,48 @@ export default {
.chat-tip {
position: absolute;
top: 400rpx;
padding: 50rpx;
line-height: 50rpx;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
padding: 40rpx;
text-align: center;
width: 80%;
.tip-icon {
width: 120rpx;
height: 120rpx;
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
border: 1rpx solid $im-bg-active;
.iconfont {
font-size: 60rpx;
color: $im-text-color-lighter;
}
}
.tip-title {
font-size: $im-font-size-large;
color: $im-text-color;
font-weight: 500;
margin-bottom: 20rpx;
}
.tip-content {
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
line-height: 1.6;
margin-bottom: 50rpx;
}
}
.chat-loading {
display: block;

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

@ -9,7 +9,12 @@
</view>
</view>
<view class="friend-tip" v-if="!hasFriends">
温馨提示您现在还没有任何好友快点击右上方'+'按钮添加好友吧~
<view class="tip-icon">
<text class="iconfont icon-add-friend"></text>
</view>
<view class="tip-title">还没有好友</view>
<view class="tip-content">添加好友开始精彩的聊天之旅</view>
<button type="primary" @click="onAddNewFriends">添加好友</button>
</view>
<view class="friend-items" v-else>
<up-index-list :index-list="friendIdx" :sticky="false" :custom-nav-height="customNavHeight">
@ -128,11 +133,49 @@ export default {
.friend-tip {
position: absolute;
top: 400rpx;
padding: 50rpx;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
padding: 60rpx 40rpx;
text-align: center;
line-height: 50rpx;
width: 80%;
max-width: 500rpx;
.tip-icon {
width: 120rpx;
height: 120rpx;
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
border: 2rpx solid $im-bg-active;
.iconfont {
font-size: 56rpx;
color: #6c757d;
opacity: 0.8;
}
}
.tip-title {
font-size: $im-font-size-large;
color: $im-text-color;
font-weight: 500;
margin-bottom: 20rpx;
}
.tip-content {
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
line-height: 1.6;
margin-bottom: 50rpx;
}
}
.friend-items {

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

@ -8,7 +8,12 @@
</view>
</view>
<view class="group-tip" v-if="!hasGroups">
温馨提示您现在还没有加入任何群聊点击右上方'+'按钮可以创建群聊哦~
<view class="tip-icon">
<text class="iconfont icon-create-group"></text>
</view>
<view class="tip-title">还没有群聊</view>
<view class="tip-content">创建或加入群聊与朋友们一起畅聊吧</view>
<button type="primary" @click="onCreateNewGroup">创建群聊</button>
</view>
<view class="group-items" v-else>
<scroll-view class="scroll-bar" scroll-with-animation="true" scroll-y="true">
@ -55,12 +60,49 @@ export default {
.group-tip {
position: absolute;
top: 400rpx;
padding: 50rpx;
text-align: left;
line-height: 50rpx;
color: darkblue;
font-size: 30rpx;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
display: flex;
flex-direction: column;
align-items: center;
padding: 60rpx 40rpx;
text-align: center;
width: 80%;
max-width: 500rpx;
.tip-icon {
width: 120rpx;
height: 120rpx;
background: linear-gradient(135deg, #f8f9fa, #e9ecef);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.08);
border: 2rpx solid $im-bg-active;
.iconfont {
font-size: 56rpx;
color: #6c757d;
opacity: 0.8;
}
}
.tip-title {
font-size: $im-font-size-large;
color: $im-text-color;
font-weight: 500;
margin-bottom: 20rpx;
}
.tip-content {
font-size: $im-font-size-smaller;
color: $im-text-color-lighter;
line-height: 1.6;
margin-bottom: 50rpx;
}
}
.group-items {

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

@ -1,58 +1,93 @@
<template>
<view class="login">
<!-- 主要内容区域 -->
<view class="content">
<!-- Logo和标题区域 -->
<view class="header">
<view class="title">欢迎登录</view>
<view class="subtitle">登录您的账号开启聊天之旅</view>
</view>
<!-- 表单区域 -->
<view class="form-container">
<view class="form">
<uni-forms :modelValue="loginForm" :rules="rules" validate-trigger="bind">
<uni-forms-item name="userName">
<uni-easyinput type="text" v-model="loginForm.userName" prefix-icon="person" placeholder="用户名" />
</uni-forms-item>
<uni-forms-item name="password">
<uni-easyinput type="password" v-model="loginForm.password" prefix-icon="locked" placeholder="密码" />
</uni-forms-item>
<button class="btn-submit" @click="submit" type="primary">登录</button>
</uni-forms>
<view class="form-item" :class="{'focused': userNameFocused}">
<view class="icon-wrapper">
<view class="icon iconfont icon-username"></view>
</view>
<input class="input" type="text" v-model="dataForm.userName" placeholder="用户名"
@focus="userNameFocused = true" @blur="userNameFocused = false" />
</view>
<view class="form-item" :class="{'focused': passwordFocused}">
<view class="icon-wrapper">
<view class="icon iconfont icon-pwd"></view>
</view>
<input class="input" :type="isShowPwd?'text':'password'" v-model="dataForm.password"
placeholder="密码" @focus="passwordFocused = true" @blur="passwordFocused = false" />
<view class="icon-suffix iconfont" :class="isShowPwd?'icon-pwd-show':'icon-pwd-hide'"
@click="onSwitchShowPwd"></view>
</view>
<navigator class="nav-register" url="/pages/register/register">
没有账号,前往注册
</view>
<!-- 登录按钮 -->
<button class="submit-btn" @click="submit" type="primary">
<view class="btn-content">
<text class="btn-text">立即登录</text>
</view>
</button>
<!-- 底部导航 -->
<view class="nav-tool-bar">
<view class="nav-register">
<navigator url="/pages/register/register" class="register-link">
<text class="register-text">没有账号</text>
<text class="register-highlight">立即注册</text>
</navigator>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import UNI_APP from '@/.env.js';
export default {
data() {
return {
loginForm: {
isShowPwd: false,
userNameFocused: false,
passwordFocused: false,
dataForm: {
terminal: 1, // APP
userName: '',
password: ''
},
rules: {
userName: {
rules: [{
required: true,
errorMessage: '请输入用户名',
}]
},
password: {
rules: [{
required: true,
errorMessage: '请输入密码',
}]
}
}
}
},
methods: {
submit() {
if (!this.dataForm.userName) {
return uni.showToast({
title: "请输入您的账号",
icon: "none"
})
}
if (!this.dataForm.password) {
return uni.showToast({
title: "请输入您的密码",
icon: "none"
})
}
this.$http({
url: '/login',
data: this.loginForm,
data: this.dataForm,
method: 'POST'
}).then(loginInfo => {
console.log("登录成功,自动跳转到聊天页面...")
uni.setStorageSync("userName", this.loginForm.userName);
uni.setStorageSync("password", this.loginForm.password);
uni.setStorageSync("userName", this.dataForm.userName);
uni.setStorageSync("password", this.dataForm.password);
uni.setStorageSync("isAgree", this.isAgree);
uni.setStorageSync("loginInfo", loginInfo);
// App.vue
getApp().$vm.init()
@ -60,44 +95,199 @@ export default {
uni.switchTab({
url: "/pages/chat/chat"
})
}).catch((e) => {
console.log("登录失败:", e);
})
}
//
getApp().$vm.unloadStore();
},
onSwitchShowPwd() {
this.isShowPwd = !this.isShowPwd;
},
},
onLoad() {
this.loginForm.userName = uni.getStorageSync("userName");
this.loginForm.password = uni.getStorageSync("password");
this.dataForm.userName = uni.getStorageSync("userName");
this.dataForm.password = uni.getStorageSync("password");
}
}
</script>
<style lang="scss" scoped>
.login {
position: relative;
display: flex;
flex-direction: column;
overflow: hidden;
//
.content {
position: relative;
display: flex;
flex-direction: column;
margin-top: 120rpx;
// #ifdef APP-PLUS
margin-top: calc(120rpx + var(--status-bar-height));
// #endif
padding: 0 60rpx;
}
//
.header {
text-align: center;
padding: 80rpx 0;
.title {
padding-top: 250rpx;
padding-bottom: 50rpx;
color: $im-color-primary;
text-align: center;
font-size: 24px;
font-weight: bold;
font-size: 48rpx;
font-weight: 700;
margin-bottom: 20rpx;
letter-spacing: 2rpx;
}
.subtitle {
color: $im-text-color-light;
font-size: 28rpx;
opacity: 0.8;
}
}
//
.form-container {
display: flex;
flex-direction: column;
}
//
.form {
padding: 50rpx;
margin-bottom: 20rpx;
.form-item {
position: relative;
display: flex;
align-items: center;
padding: 0 30rpx;
height: 100rpx;
margin: 24rpx 0;
border-radius: 25rpx;
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid transparent;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
.btn-submit {
margin-top: 80rpx;
&.focused {
border-color: $im-color-primary;
box-shadow: 0 8rpx 32rpx rgba($im-color-primary, 0.15);
transform: translateY(-2rpx);
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 60rpx;
margin-right: 30rpx;
border-radius: 50%;
background: $im-bg-active;
transition: all 0.3s ease;
.icon {
font-size: 32rpx;
color: $im-color-primary;
font-weight: bold;
}
}
&.focused .icon-wrapper {
transform: scale(1.1);
}
.input {
flex: 1;
font-size: 32rpx;
color: #333;
background: transparent;
border: none;
outline: none;
&::placeholder {
color: $im-text-color-light;
font-size: 30rpx;
}
}
.icon-suffix {
font-size: 36rpx;
padding: 10rpx;
}
}
}
//
.submit-btn {
height: 100rpx;
border-radius: 50rpx;
border: none;
box-shadow: 0 8rpx 32rpx rgba($im-color-primary, 0.3);
transition: all 0.3s ease;
overflow: hidden;
position: relative;
width: 100%;
&:active {
transform: translateY(2rpx);
box-shadow: 0 4rpx 16rpx rgba($im-color-primary, 0.4);
&::before {
left: 100%;
}
}
.btn-content {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: white;
font-size: $im-font-size-large;
font-weight: 600;
.btn-text {
margin-right: 10rpx;
}
}
&:active .btn-icon {
transform: translateX(4rpx);
}
}
//
.nav-tool-bar {
padding: 40rpx 0 60rpx;
display: flex;
align-items: center;
justify-content: space-between;
.nav-register {
position: fixed;
width: 100%;
bottom: 100rpx;
.register-link {
display: flex;
align-items: center;
text-decoration: none;
.register-text {
color: $im-text-color-light;
font-size: $im-font-size-small;
margin-right: 8rpx;
}
.register-highlight {
color: $im-color-primary;
text-align: center;
font-size: $im-font-size;
font-size: $im-font-size-small;
font-weight: 600;
}
}
}
}
}
</style>

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

@ -1,89 +1,143 @@
<template>
<view class="register">
<view class="title">欢迎注册</view>
<!-- 主要内容区域 -->
<view class="content">
<!-- Logo和标题区域 -->
<view class="header" @longpress.prevent="onLongPressHeader()">
<view class="title"> 用户注册 </view>
<view class="subtitle">创建您的账号开始精彩旅程</view>
</view>
<!-- 表单区域 -->
<view class="form-container">
<view class="form">
<uni-forms ref="form" :modelValue="dataForm" :rules="rules" validate-trigger="bind" label-width="80px">
<uni-forms-item name="userName" label="用户名">
<uni-easyinput type="text" v-model="dataForm.userName" placeholder="用户名" maxlength="20"/>
</uni-forms-item>
<uni-forms-item name="nickName" label="昵称">
<uni-easyinput type="text" v-model="dataForm.nickName" placeholder="昵称" maxlength="20"/>
</uni-forms-item>
<uni-forms-item name="password" label="密码">
<uni-easyinput type="password" v-model="dataForm.password" placeholder="密码" maxlength="20"/>
</uni-forms-item>
<uni-forms-item name="corfirmPassword" label="确认密码">
<uni-easyinput type="password" v-model="dataForm.corfirmPassword" placeholder="确认密码" maxlength="20"/>
</uni-forms-item>
<button class="btn-submit" @click="submit" type="primary">注册并登录</button>
</uni-forms>
</view>
<navigator class="nav-login" url="/pages/login/login">
返回登录页面
<!-- 用户名输入 -->
<view class="form-item" :class="{'focused': userNameFocused}">
<view class="icon-wrapper">
<view class="icon iconfont icon-username"></view>
</view>
<input class="input" type="text" v-model="dataForm.userName" placeholder="请填写用户名"
@focus="userNameFocused = true" @blur="userNameFocused = false" />
</view>
<!-- 用户名输入 -->
<view class="form-item" :class="{'focused': nickNameFocused}">
<view class="icon-wrapper">
<view class="icon iconfont icon-username"></view>
</view>
<input class="input" type="text" v-model="dataForm.nickName" placeholder="请填写昵称"
@focus="nickNameFocused = true" @blur="nickNameFocused = false" />
</view>
<!-- 密码输入 -->
<view class="form-item" :class="{'focused': passwordFocused}">
<view class="icon-wrapper">
<view class="icon iconfont icon-pwd"></view>
</view>
<input class="input" :type="isShowPwd?'text':'password'" v-model="dataForm.password"
placeholder="请设置密码" @focus="passwordFocused = true" @blur="passwordFocused = false" />
<view class="icon-suffix iconfont" :class="isShowPwd?'icon-pwd-show':'icon-pwd-hide'"
@click="onSwitchShowPwd"></view>
</view>
<!-- 确认密码输入 -->
<view class="form-item" :class="{'focused': confirmPasswordFocused}">
<view class="icon-wrapper">
<view class="icon iconfont icon-pwd"></view>
</view>
<input class="input" :type="isShowConfirmPwd?'text':'password'"
v-model="dataForm.confirmPassword" placeholder="确认密码" @focus="confirmPasswordFocused = true"
@blur="confirmPasswordFocused = false" />
<view class="icon-suffix iconfont" :class="isShowConfirmPwd?'icon-pwd-show':'icon-pwd-hide'"
@click="onSwitchShowConfirmPwd"></view>
</view>
<!-- 注册按钮 -->
<button class="submit-btn" @click="submit" type="primary">
<view class="btn-content">
<text class="btn-text">注册并登录</text>
</view>
</button>
<!-- 底部导航 -->
<view class="nav-tool-bar">
<view class="nav-login">
<navigator url="/pages/login/login" class="login-link">
<text class="login-text">已有账号</text>
<text class="login-highlight">立即登录</text>
</navigator>
</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
import UNI_APP from '@/.env.js';
export default {
data() {
return {
isShowPwd: false,
isShowConfirmPwd: false,
userNameFocused: false,
nickNameFocused: false,
passwordFocused: false,
confirmPasswordFocused: false,
dataForm: {
userName: '',
nickName: '',
password: '',
corfirmPassword: ''
},
rules: {
userName: {
rules: [{
required: true,
errorMessage: '请输入用户名',
}]
confirmPassword: ''
}
}
},
nickName: {
rules: [{
required: true,
errorMessage: '请输入昵称',
}]
methods: {
onSwitchShowPwd() {
this.isShowPwd = !this.isShowPwd;
},
password: {
rules: [{
required: true,
errorMessage: '请输入密码',
}]
onSwitchShowConfirmPwd() {
this.isShowConfirmPwd = !this.isShowConfirmPwd;
},
corfirmPassword: {
rules: [{
required: true,
errorMessage: '请输入确认密码',
}, {
validateFunction: (rule, value, data, callback) => {
if (data.password != value) {
callback('两次密码输入不一致')
}
return true;
submit() {
if (!this.dataForm.userName) {
return uni.showToast({
title: '请输入用户名',
icon: 'none'
});
}
}]
if (!this.dataForm.password) {
return uni.showToast({
title: '请输入密码',
icon: 'none'
});
}
if (!this.dataForm.confirmPassword) {
return uni.showToast({
title: '请输入确认密码',
icon: 'none'
});
}
if (this.dataForm.confirmPassword != this.dataForm.password) {
return uni.showToast({
title: '两次密码输入不一致',
icon: 'none'
});
}
},
methods: {
submit() {
this.$refs.form.validate().then(() => {
this.$http({
url: '/register',
data: this.dataForm,
method: 'POST'
}).then(() => {
let appName = uni.getSystemInfoSync().appName
uni.showToast({
title: "注册成功,您已成为盒子IM的用户",
title: `注册成功,您已成为${appName}的用户`,
icon: 'none'
})
this.login();
})
})
},
login() {
const loginForm = {
@ -107,6 +161,8 @@ export default {
url: "/pages/chat/chat"
})
})
//
getApp().$vm.unloadStore();
}
}
}
@ -114,31 +170,182 @@ export default {
<style lang="scss" scoped>
.register {
display: flex;
flex-direction: column;
position: relative;
overflow: hidden;
//
.content {
flex: 1;
position: relative;
display: flex;
flex-direction: column;
margin-top: 120rpx;
// #ifdef APP-PLUS
margin-top: calc(120rpx + var(--status-bar-height));
// #endif
padding: 0 60rpx;
}
//
.header {
text-align: center;
padding: 80rpx 0;
.title {
padding-top: 250rpx;
padding-bottom: 50rpx;
color: $im-color-primary;
text-align: center;
font-size: 24px;
font-weight: 600;
font-size: 48rpx;
font-weight: 700;
margin-bottom: 20rpx;
letter-spacing: 2rpx;
}
.subtitle {
color: $im-text-color-light;
font-size: 28rpx;
opacity: 0.8;
}
}
//
.form-container {
display: flex;
flex-direction: column;
}
//
.form {
padding: 50rpx;
height: 830rpx;
display: flex;
flex-direction: column;
justify-content: center;
.form-item {
position: relative;
display: flex;
align-items: center;
padding: 0 30rpx;
height: 100rpx;
margin: 14rpx 0;
border-radius: 25rpx;
background: rgba(255, 255, 255, 0.9);
border: 2rpx solid transparent;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
&.focused {
border-color: $im-color-primary;
box-shadow: 0 8rpx 32rpx rgba($im-color-primary, 0.15);
transform: translateY(-2rpx);
}
.icon-wrapper {
display: flex;
align-items: center;
justify-content: center;
width: 60rpx;
height: 60rpx;
margin-right: 30rpx;
border-radius: 50%;
background: $im-bg-active;
transition: all 0.3s ease;
.icon {
font-size: 32rpx;
color: $im-color-primary;
font-weight: bold;
}
}
&.focused .icon-wrapper {
transform: scale(1.1);
}
.input {
flex: 1;
font-size: 32rpx;
color: #333;
background: transparent;
border: none;
outline: none;
&::placeholder {
color: $im-text-color-light;
font-size: 30rpx;
}
}
.icon-suffix {
font-size: 36rpx;
padding: 10rpx;
}
}
}
.btn-submit {
margin-top: 80rpx;
//
.submit-btn {
height: 100rpx;
border-radius: 50rpx;
border: none;
box-shadow: 0 8rpx 32rpx rgba($im-color-primary, 0.3);
transition: all 0.3s ease;
overflow: hidden;
position: relative;
width: 100%;
margin-top: 50rpx;
&:active {
transform: translateY(2rpx);
box-shadow: 0 4rpx 16rpx rgba($im-color-primary, 0.4);
}
.btn-content {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: white;
font-size: $im-font-size-large;
font-weight: 600;
.btn-text {
margin-right: 10rpx;
}
}
}
//
.nav-tool-bar {
padding: 40rpx 0 60rpx;
display: flex;
align-items: center;
justify-content: center;
.nav-login {
position: fixed;
width: 100%;
bottom: 100rpx;
.login-link {
display: flex;
align-items: center;
text-decoration: none;
.login-text {
color: $im-text-color-light;
font-size: $im-font-size-small;
margin-right: 8rpx;
}
.login-highlight {
color: $im-color-primary;
text-align: center;
font-size: 32rpx;
font-size: $im-font-size-small;
font-weight: 600;
transition: all 0.3s ease;
&:active {
opacity: 0.7;
}
}
}
}
}
}
</style>

102
im-uniapp/static/icon/iconfont.css

@ -1,6 +1,6 @@
@font-face {
font-family: "iconfont"; /* Project id 4272106 */
src: url('iconfont.ttf?t=1750317465456') format('truetype');
src: url('iconfont.ttf?t=1759053579007') format('truetype');
}
.iconfont {
@ -11,20 +11,92 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-dnd:before {
content: "\e693";
.icon-chat:before {
content: "\e93e";
}
.icon-privacy-protocol:before {
content: "\e761";
.icon-create-group:before {
content: "\e65b";
}
.icon-create-group-2:before {
content: "\e616";
.icon-refresh:before {
content: "\e64c";
}
.icon-create-group:before {
content: "\e650";
.icon-copy:before {
content: "\e604";
}
.icon-code:before {
content: "\e72c";
}
.icon-username:before {
content: "\e623";
}
.icon-pwd:before {
content: "\e60d";
}
.icon-pwd-hide:before {
content: "\e60c";
}
.icon-username2:before {
content: "\e647";
}
.icon-pwd-show:before {
content: "\e621";
}
.icon-teenager:before {
content: "\eba1";
}
.icon-warning-circle-empty:before {
content: "\e606";
}
.icon-loading2:before {
content: "\e6b6";
}
.icon-complaint:before {
content: "\e612";
}
.icon-about:before {
content: "\e637";
}
.icon-security:before {
content: "\e61d";
}
.icon-personal-info:before {
content: "\e62d";
}
.icon-success:before {
content: "\e649";
}
.icon-wait:before {
content: "\e701";
}
.icon-error:before {
content: "\e62b";
}
.icon-dnd:before {
content: "\e693";
}
.icon-create-group-2:before {
content: "\e616";
}
.icon-qrcode:before {
@ -67,10 +139,6 @@
content: "\e611";
}
.icon-username:before {
content: "\e60f";
}
.icon-chat-muted:before {
content: "\e634";
}
@ -87,18 +155,10 @@
content: "\e63c";
}
.icon-user-protocol:before {
content: "\e61a";
}
.icon-film:before {
content: "\e66b";
}
.icon-chat:before {
content: "\e624";
}
.icon-delete:before {
content: "\e605";
}

BIN
im-uniapp/static/icon/iconfont.ttf

Binary file not shown.

9
im-uniapp/store/chatStore.js

@ -519,11 +519,14 @@ export default defineStore('chatStore', {
}
}
}
// 正在发送中的消息可能没有id,只有tmpId
if (msgInfo.tmpId) {
// 正在发送中的临时消息可能没有id,只有tmpId
if (msgInfo.selfSend && msgInfo.tmpId) {
for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
let m = chat.messages[idx];
if (m.tmpId && msgInfo.tmpId == m.tmpId) {
if (!m.selfSend || !m.tmpId) {
continue;
}
if (msgInfo.tmpId == m.tmpId) {
return m;
}
// 如果id比要查询的消息小,说明没有这条消息

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

@ -104,9 +104,27 @@ $--font-family: Microsoft YaHei, 'Avenir', Helvetica, Arial, sans-serif;
font-family: $--font-family;
}
.el-tag--mini {
height: 18px;
padding: 0 2px;
.el-tag {
font-size: 10px;
height: 16px;
line-height: 16px;
border-radius: 2px;
text-align: center;
border-radius: 5px;
border: 0;
margin-left: 3px;
padding: 0 3px !important;
color: white !important;
&.el-tag--primary {
background: var(--im-color-primary-light-4);
}
&.el-tag--warning {
background: var(--im-color-warning);
}
&.el-tag--danger {
background: var(--im-color-danger);
}
}

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

@ -44,9 +44,9 @@
--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;
--im-background: white;
--im-background-active: #f4f4fc;
--im-background-active-dark: var(--im-color-primary-light-9);
}
html {
@ -83,6 +83,12 @@ section {
border-radius: 4px;
}
.el-scrollbar__thumb {
border-radius: 4px;
background: var(--im-color-primary-light-9) !important;
}
.search-input {
.el-input__inner {
border: unset !important;

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

@ -883,7 +883,7 @@ export default {
.im-chat-main {
padding: 0;
background-color: #fff;
background-color: #f4f5f6;
.im-chat-box {
>ul {

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

@ -9,7 +9,7 @@
<div class="chat-name">
<div class="chat-name-text">
<div>{{ chat.showName }}</div>
<el-tag v-if="chat.type == 'GROUP'" size="mini"></el-tag>
<el-tag v-if="chat.type == 'GROUP'" type="primary" size="mini"></el-tag>
</div>
<div class="chat-time-text">{{ showTime }}</div>
</div>
@ -117,14 +117,16 @@ export default {
<style lang="scss" scoped>
.chat-item {
height: 50px;
height: 56px;
display: flex;
position: relative;
padding: 5px 10px;
margin: 0 3px;
padding: 5px 8px;
align-items: center;
background-color: var(--im-background);
white-space: nowrap;
cursor: pointer;
border-radius: 10px;
&:hover {
background-color: var(--im-background-active);
@ -166,8 +168,8 @@ export default {
.chat-name {
display: flex;
line-height: 20px;
height: 20px;
line-height: 26px;
height: 26px;
.chat-name-text {
flex: 1;
@ -176,14 +178,6 @@ export default {
font-size: var(--im-font-size);
white-space: nowrap;
overflow: hidden;
.el-tag {
min-width: 22px;
text-align: center;
border-radius: 10px;
border: 0;
height: 16px;
}
}
.chat-time-text {
@ -198,7 +192,7 @@ export default {
.chat-content {
display: flex;
line-height: 22px;
line-height: 24px;
.chat-at-text {
color: #c70b0b;

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

@ -301,18 +301,6 @@ export default {
white-space: pre-wrap;
word-break: break-word;
&:after {
content: "";
position: absolute;
left: -10px;
top: 13px;
width: 0;
height: 0;
border-style: solid dashed dashed;
border-color: #eee transparent transparent;
overflow: hidden;
border-width: 10px;
}
}
.message-image {
@ -320,6 +308,7 @@ export default {
border: 2px solid var(--im-color-primary-light-9);
overflow: hidden;
cursor: pointer;
background: var(--im-background);
}
.message-file {
@ -329,6 +318,7 @@ export default {
align-items: center;
cursor: pointer;
margin-bottom: 2px;
background: var(--im-background);
.chat-file-box {
display: flex;
@ -466,12 +456,6 @@ export default {
.message-text {
background-color: var(--im-color-primary-light-2);
color: #fff;
&:after {
left: auto;
right: -10px;
border-top-color: var(--im-color-primary-light-2);
}
}
.chat-action {

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

@ -64,12 +64,23 @@ export default {
}
.close {
display: flex;
align-items: center;
justify-content: center;
position: fixed;
top: 10px;
right: 10px;
color: white;
font-size: 25px;
top: 20px;
right: 20px;
cursor: pointer;
background: #333;
border-radius: 50%;
padding: 10px;
opacity: 0.5;
i {
font-weight: bold;
font-size: 20px;
color: white;
}
}
}
</style>

131
im-web/src/components/common/ResizableAside.vue

@ -0,0 +1,131 @@
<template>
<el-aside :style="{ width: asideWidth + 'px' }" class="resizable-aside">
<slot></slot>
<!-- 拖拽条 -->
<div class="resize-handle" @mousedown="startResize" :class="{ 'resizing': isResizing }" title="拖拽调整宽度">
<div class="resize-line"></div>
</div>
</el-aside>
</template>
<script>
export default {
name: "ResizableAside",
props: {
//
defaultWidth: {
type: Number,
default: 260
},
//
minWidth: {
type: Number,
default: 200
},
//
maxWidth: {
type: Number,
default: 500
},
// localStoragekey
storageKey: {
type: String,
required: true
}
},
data() {
return {
asideWidth: this.defaultWidth,
isResizing: false,
startX: 0,
startWidth: 0
}
},
mounted() {
// localStorage
const savedWidth = localStorage.getItem(this.storageKey);
if (savedWidth) {
this.asideWidth = parseInt(savedWidth);
}
//
document.addEventListener('mousemove', this.handleResize);
document.addEventListener('mouseup', this.stopResize);
},
beforeDestroy() {
//
document.removeEventListener('mousemove', this.handleResize);
document.removeEventListener('mouseup', this.stopResize);
},
methods: {
//
startResize(e) {
this.isResizing = true;
this.startX = e.clientX;
this.startWidth = this.asideWidth;
document.body.style.cursor = 'col-resize';
document.body.style.userSelect = 'none';
e.preventDefault();
},
handleResize(e) {
if (!this.isResizing) return;
const deltaX = e.clientX - this.startX;
let newWidth = this.startWidth + deltaX;
//
newWidth = Math.max(this.minWidth, Math.min(this.maxWidth, newWidth));
this.asideWidth = newWidth;
},
stopResize() {
if (!this.isResizing) return;
this.isResizing = false;
document.body.style.cursor = '';
document.body.style.userSelect = '';
// localStorage
localStorage.setItem(this.storageKey, this.asideWidth.toString());
}
}
}
</script>
<style lang="scss" scoped>
.resizable-aside {
display: flex;
flex-direction: column;
border-right: 1px solid rgba(0, 0, 0, 0.08);
box-shadow: 2px 0 8px rgba(0, 0, 0, 0.05);
position: relative;
//
.resize-handle {
position: absolute;
top: 0;
right: -3px;
width: 6px;
height: 100%;
cursor: col-resize;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease;
.resize-line {
width: 2px;
height: 100%;
background-color: var(--im-background-active-dark);
border-radius: 1px;
transition: all 0.2s ease;
}
&:hover .resize-line,
&.resizing .resize-line {
width: 3px;
}
}
}
</style>

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

@ -21,7 +21,7 @@
<div>用户名:{{ user.userName }}</div>
</div>
</div>
<el-button type="success" size="mini" v-show="!isFriend(user.id)"
<el-button type="primary" 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>

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

@ -74,9 +74,11 @@ export default {
height: 50px;
display: flex;
position: relative;
padding: 5px 10px;
align-items: center;
white-space: nowrap;
border-radius: 10px;
margin: 0 3px;
padding: 5px 8px;
cursor: pointer;
&:hover {

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

@ -36,9 +36,9 @@ export default {
height: 50px;
display: flex;
position: relative;
padding: 5px 10px;
align-items: center;
white-space: nowrap;
border-radius: 10px;
margin: 0 3px;
padding: 5px 8px;
cursor: pointer;
&:hover {

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

@ -45,13 +45,15 @@ export default {
align-items: center;
white-space: nowrap;
box-sizing: border-box;
border-radius: 5px;
margin: 0 1px;
&:hover {
background-color: var(--im-background-active);
}
&.active {
background-color: #eeeeee;
background-color: var(--im-background-active-dark);
}
.member-name {

9
im-web/src/store/chatStore.js

@ -547,11 +547,14 @@ export default defineStore('chatStore', {
}
}
}
// 正在发送中的消息可能没有id,只有tmpId
if (msgInfo.tmpId) {
// 正在发送中的临时消息可能没有id,只有tmpId
if (msgInfo.selfSend && msgInfo.tmpId) {
for (let idx = chat.messages.length - 1; idx >= 0; idx--) {
let m = chat.messages[idx];
if (m.tmpId && msgInfo.tmpId == m.tmpId) {
if (!m.selfSend || !m.tmpId) {
continue;
}
if (msgInfo.tmpId == m.tmpId) {
return m;
}
// 如果id比要查询的消息小,说明没有这条消息

4
im-web/src/store/configStore.js

@ -5,6 +5,7 @@ export default defineStore('configStore', {
state: () => {
return {
appInit: false, // 应用是否完成初始化
fullScreen: true, // 当前是否全屏
webrtc: {}
}
},
@ -15,6 +16,9 @@ export default defineStore('configStore', {
setAppInit(appInit) {
this.appInit = appInit;
},
setFullScreen(fullScreen) {
this.fullScreen = fullScreen;
},
loadConfig() {
return new Promise((resolve, reject) => {
http({

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

@ -1,6 +1,6 @@
<template>
<el-container class="chat-page">
<el-aside width="260px" class="aside">
<resizable-aside :default-width="260" :min-width="200" :max-width="500" storage-key="chat-aside-width">
<div class="header">
<el-input class="search-text" size="small" placeholder="搜索" v-model="searchText">
<i class="el-icon-search el-input__icon" slot="prefix"> </i>
@ -16,7 +16,7 @@
@dnd="onDnd(chat)" :active="chat === chatStore.activeChat"></chat-item>
</div>
</el-scrollbar>
</el-aside>
</resizable-aside>
<el-container>
<chat-box v-if="chatStore.activeChat" :chat="chatStore.activeChat"></chat-box>
</el-container>
@ -26,12 +26,14 @@
<script>
import ChatItem from "../components/chat/ChatItem.vue";
import ChatBox from "../components/chat/ChatBox.vue";
import ResizableAside from "../components/common/ResizableAside.vue";
export default {
name: "chat",
components: {
ChatItem,
ChatBox
ChatBox,
ResizableAside
},
data() {
return {
@ -97,10 +99,6 @@ export default {
<style lang="scss" scoped>
.chat-page {
.aside {
display: flex;
flex-direction: column;
background: var(--im-background);
.header {
height: 50px;
@ -126,6 +124,6 @@ export default {
.chat-items {
flex: 1;
}
}
}
</style>

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

@ -1,6 +1,6 @@
<template>
<el-container class="friend-page">
<el-aside width="260px" class="aside">
<resizable-aside :default-width="260" :min-width="200" :max-width="500" storage-key="friend-aside-width">
<div class="header">
<el-input class="search-text" size="small" placeholder="搜索" v-model="searchText">
<i class="el-icon-search el-input__icon" slot="prefix"> </i>
@ -21,7 +21,7 @@
<div v-if="i < friendValues.length - 1" class="divider"></div>
</div>
</el-scrollbar>
</el-aside>
</resizable-aside>
<el-container class="container">
<div class="header" v-show="userInfo.id">
{{ userInfo.nickName }}
@ -61,6 +61,7 @@
import FriendItem from "../components/friend/FriendItem.vue";
import AddFriend from "../components/friend/AddFriend.vue";
import HeadImage from "../components/common/HeadImage.vue";
import ResizableAside from "../components/common/ResizableAside.vue";
import { pinyin } from 'pinyin-pro';
export default {
@ -68,8 +69,8 @@ export default {
components: {
FriendItem,
AddFriend,
HeadImage
HeadImage,
ResizableAside
},
data() {
return {
@ -223,10 +224,6 @@ export default {
<style lang="scss" scoped>
.friend-page {
.aside {
display: flex;
flex-direction: column;
background: var(--im-background);
.header {
height: 50px;
@ -290,5 +287,4 @@ export default {
padding: 20px;
}
}
}
</style>

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

@ -1,6 +1,6 @@
<template>
<el-container class="group-page">
<el-aside width="260px" class="aside">
<resizable-aside :default-width="260" :min-width="200" :max-width="500" storage-key="group-aside-width">
<div class="header">
<el-input class="search-text" size="small" placeholder="搜索" v-model="searchText">
<i class="el-icon-search el-input__icon" slot="prefix"> </i>
@ -18,7 +18,7 @@
<div v-if="i < groupValues.length - 1" class="divider"></div>
</div>
</el-scrollbar>
</el-aside>
</resizable-aside>
<el-container class="container">
<div class="header" v-show="activeGroup.id">{{ activeGroup.showGroupName }}({{ showMembers.length }})</div>
<div class="container-box" v-show="activeGroup.id">
@ -101,6 +101,7 @@ import GroupMember from '../components/group/GroupMember.vue';
import AddGroupMember from '../components/group/AddGroupMember.vue';
import GroupMemberSelector from '../components/group/GroupMemberSelector.vue';
import HeadImage from '../components/common/HeadImage.vue';
import ResizableAside from "../components/common/ResizableAside.vue";
import { pinyin } from 'pinyin-pro';
export default {
@ -111,7 +112,8 @@ export default {
FileUpload,
AddGroupMember,
GroupMemberSelector,
HeadImage
HeadImage,
ResizableAside
},
data() {
return {
@ -349,10 +351,6 @@ export default {
<style lang="scss" scoped>
.group-page {
.aside {
display: flex;
flex-direction: column;
background: var(--im-background);
.header {
height: 50px;
@ -378,7 +376,6 @@ export default {
color: var(--im-text-color-light);
}
}
}
.container {
display: flex;

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

@ -1,6 +1,6 @@
<template>
<div class="home-page" @click="closeUserInfo">
<div class="app-container" :class="{ fullscreen: isFullscreen }">
<div class="app-container" :class="{ fullscreen: configStore.fullScreen }">
<div class="navi-bar">
<div class="navi-bar-box">
<div class="top">
@ -30,7 +30,7 @@
</div>
<div class="botoom">
<div class="bottom-item" @click="isFullscreen = !isFullscreen">
<div class="bottom-item" @click="onSwtichFullScreen">
<i class="el-icon-full-screen"></i>
</div>
<div class="bottom-item" @click="showSetting">
@ -77,7 +77,6 @@ export default {
return {
showSettingDialog: false,
lastPlayAudioTime: new Date().getTime() - 1000,
isFullscreen: true,
reconnecting: false,
privateMessagesBuffer: [],
groupMessagesBuffer: []
@ -149,9 +148,11 @@ export default {
this.$wsApi.onClose((e) => {
if (e.code != 3000) {
// 线
if (!this.reconnecting) {
this.reconnectWs();
this.configStore.setAppInit(false)
}
}
});
}).catch((e) => {
console.log("初始化失败", e);
@ -386,8 +387,8 @@ export default {
this.chatStore.insertMessage(msg, chatInfo);
//
if (!group.isDnd && !this.chatStore.loading &&
!msg.selfSend && this.$msgType.isNormal(msg.type)
&& msg.status != this.$enums.MESSAGE_STATUS.READED) {
!msg.selfSend && this.$msgType.isNormal(msg.type) &&
msg.status != this.$enums.MESSAGE_STATUS.READED) {
this.playAudioTip();
}
},
@ -407,6 +408,9 @@ export default {
closeUserInfo() {
this.$refs.userInfo.close();
},
onSwtichFullScreen() {
this.configStore.setFullScreen(!this.configStore.fullScreen);
},
onExit() {
this.unloadStore();
this.configStore.setAppInit(false);
@ -538,35 +542,38 @@ export default {
}
.menu {
height: 200px;
//margin-top: 10px;
display: flex;
flex-direction: column;
justify-content: center;
align-content: center;
flex-wrap: wrap;
margin-top: 20px;
.link {
text-decoration: none;
}
.router-link-active .menu-item {
color: #fff;
color: white;
background: var(--im-color-primary-light-2);
}
.link:not(.router-link-active) .menu-item:hover {
color: var(--im-color-primary-light-7);
background: var(--im-color-primary);
transform: scale(1.1);
}
.menu-item {
position: relative;
color: var(--im-color-primary-light-4);
color: #eee;
width: var(--width);
height: 46px;
width: 46px;
display: flex;
justify-content: center;
align-items: center;
margin-bottom: 12px;
margin-top: 30px;
border-radius: 10px;
.icon {
font-size: var(--icon-font-size)

Loading…
Cancel
Save