Browse Source

!163 最大群人数改完3000

Merge pull request !163 from blue/v_3.0.0
master
blue 7 months ago
committed by Gitee
parent
commit
0657766ae8
No known key found for this signature in database GPG Key ID: 173E9B9CA92EEF8F
  1. 8
      README.md
  2. 2
      im-platform/src/main/java/com/bx/implatform/contant/Constant.java
  3. 4
      im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java
  4. 43
      im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java
  5. 10
      im-platform/src/main/java/com/bx/implatform/util/FileUtil.java
  6. 7
      im-uniapp/App.vue
  7. 35
      im-uniapp/store/chatStore.js
  8. 16
      im-web/src/components/common/RightMenu.vue
  9. 36
      im-web/src/store/chatStore.js
  10. 75
      im-web/src/view/Login.vue
  11. 133
      im-web/src/view/Register.vue

8
README.md

@ -21,7 +21,7 @@
- 后台管理端上线,后台管理代码仓库地址:https://gitee.com/bluexsx/box-im-admin - 后台管理端上线,后台管理代码仓库地址:https://gitee.com/bluexsx/box-im-admin
- 框架和组件版本全面升级: jdk17、springboot3.3、node18等 - 框架和组件版本全面升级: jdk17、springboot3.3、node18等
- 部分界面,功能、性能优化1 - 部分界面,功能、性能优化
#### 在线体验 #### 在线体验
@ -39,10 +39,12 @@
![输入图片说明](%E6%88%AA%E5%9B%BE/h5%E4%BA%8C%E7%BB%B4%E7%A0%81.png) ![输入图片说明](%E6%88%AA%E5%9B%BE/h5%E4%BA%8C%E7%BB%B4%E7%A0%81.png)
说明: 说明:
1.由于微信小程序每次发布审核过于严苛和繁琐,暂时不再提供体验环境,但uniapp端依然会继续兼容小程序 1.**请勿利用公开账号辱骂他人、发布低俗内容,否则将直接对您的IP进行封禁**
2.体验环境部署的是商业版本,与开源版本功能存在一定差异,具体请参考: 2.由于微信小程序每次发布审核过于严苛和繁琐,暂时不再提供体验环境,但uniapp端依然会继续兼容小程序
3.体验环境部署的是商业版本,与开源版本功能存在一定差异,具体请参考:
https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29#SbvXq https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29#SbvXq
#### 付费服务 #### 付费服务
商业版源码: https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29 商业版源码: https://www.yuque.com/u1475064/imk5n2/qtezcg32q1d0dr29
远程协助: https://www.yuque.com/u1475064/imk5n2/fettd57rvzc29s5r 远程协助: https://www.yuque.com/u1475064/imk5n2/fettd57rvzc29s5r

2
im-platform/src/main/java/com/bx/implatform/contant/Constant.java

@ -18,7 +18,7 @@ public final class Constant {
/** /**
* 大群人数上限 * 大群人数上限
*/ */
public static final Long MAX_LARGE_GROUP_MEMBER = 10000L; public static final Long MAX_LARGE_GROUP_MEMBER = 3000L;
/** /**
* 普通群人数上限 * 普通群人数上限

4
im-platform/src/main/java/com/bx/implatform/service/impl/GroupMessageServiceImpl.java

@ -67,10 +67,10 @@ public class GroupMessageServiceImpl extends ServiceImpl<GroupMessageMapper, Gro
} }
// 群聊成员列表 // 群聊成员列表
List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId()); List<Long> userIds = groupMemberService.findUserIdsByGroupId(group.getId());
if (dto.getReceipt() && userIds.size() > Constant.MAX_LARGE_GROUP_MEMBER) { if (dto.getReceipt() && userIds.size() > Constant.MAX_NORMAL_GROUP_MEMBER) {
// 大群的回执消息过于消耗资源,不允许发送 // 大群的回执消息过于消耗资源,不允许发送
throw new GlobalException( throw new GlobalException(
String.format("当前群聊大于%s人,不支持发送回执消息", Constant.MAX_LARGE_GROUP_MEMBER)); String.format("当前群聊大于%s人,不支持发送回执消息", Constant.MAX_NORMAL_GROUP_MEMBER));
} }
// 不用发给自己 // 不用发给自己
userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList()); userIds = userIds.stream().filter(id -> !session.getUserId().equals(id)).collect(Collectors.toList());

43
im-platform/src/main/java/com/bx/implatform/thirdparty/MinioService.java

@ -1,6 +1,8 @@
package com.bx.implatform.thirdparty; package com.bx.implatform.thirdparty;
import cn.hutool.core.util.RandomUtil;
import com.bx.implatform.util.DateTimeUtils; import com.bx.implatform.util.DateTimeUtils;
import com.bx.implatform.util.FileUtil;
import io.minio.*; import io.minio.*;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
@ -38,9 +40,7 @@ public class MinioService {
*/ */
public void makeBucket(String bucketName) { public void makeBucket(String bucketName) {
try { try {
minioClient.makeBucket(MakeBucketArgs.builder() minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
.bucket(bucketName)
.build());
} catch (Exception e) { } catch (Exception e) {
log.error("创建bucket失败,", e); log.error("创建bucket失败,", e);
} }
@ -52,18 +52,9 @@ public class MinioService {
public void setBucketPublic(String bucketName) { public void setBucketPublic(String bucketName) {
try { try {
// 设置公开 // 设置公开
String sb = "{\"Version\":\"2012-10-17\"," + String sb =
"\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" + "{\"Version\":\"2012-10-17\"," + "\"Statement\":[{\"Effect\":\"Allow\",\"Principal\":" + "{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," + "\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" + bucketName + "/*\"]}]}";
"{\"AWS\":[\"*\"]},\"Action\":[\"s3:ListBucket\",\"s3:ListBucketMultipartUploads\"," + minioClient.setBucketPolicy(SetBucketPolicyArgs.builder().bucket(bucketName).config(sb).build());
"\"s3:GetBucketLocation\"],\"Resource\":[\"arn:aws:s3:::" + bucketName +
"\"]},{\"Effect\":\"Allow\",\"Principal\":{\"AWS\":[\"*\"]},\"Action\":[\"s3:PutObject\",\"s3:AbortMultipartUpload\",\"s3:DeleteObject\",\"s3:GetObject\",\"s3:ListMultipartUploadParts\"],\"Resource\":[\"arn:aws:s3:::" +
bucketName +
"/*\"]}]}";
minioClient.setBucketPolicy(
SetBucketPolicyArgs.builder()
.bucket(bucketName)
.config(sb)
.build());
} catch (Exception e) { } catch (Exception e) {
log.error("创建bucket失败,", e); log.error("创建bucket失败,", e);
} }
@ -82,10 +73,10 @@ public class MinioService {
if (StringUtils.isBlank(originalFilename)) { if (StringUtils.isBlank(originalFilename)) {
throw new RuntimeException(); throw new RuntimeException();
} }
String fileName = System.currentTimeMillis() + ""; // 加入随机数,防止文件重名
if (originalFilename.lastIndexOf(".") >= 0) { String fileName = FileUtil.excludeExtension(originalFilename);
fileName += originalFilename.substring(originalFilename.lastIndexOf(".")); fileName += "_" + RandomUtil.randomString(4);
} fileName += "." + FileUtil.getFileExtension(originalFilename);
String objectName = DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.PARTDATEFORMAT) + "/" + fileName; String objectName = DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.PARTDATEFORMAT) + "/" + fileName;
try { try {
InputStream stream = new ByteArrayInputStream(file.getBytes()); InputStream stream = new ByteArrayInputStream(file.getBytes());
@ -111,13 +102,15 @@ public class MinioService {
* @return objectName * @return objectName
*/ */
public String upload(String bucketName, String path, String name, byte[] fileByte, String contentType) { public String upload(String bucketName, String path, String name, byte[] fileByte, String contentType) {
// 加入随机数,防止文件重名
String fileName = System.currentTimeMillis() + name.substring(name.lastIndexOf(".")); String fileName = FileUtil.excludeExtension(name);
fileName += "_" + RandomUtil.randomString(4);
fileName += "." + FileUtil.getFileExtension(name);
String objectName = DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.PARTDATEFORMAT) + "/" + fileName; String objectName = DateTimeUtils.getFormatDate(new Date(), DateTimeUtils.PARTDATEFORMAT) + "/" + fileName;
try { try {
InputStream stream = new ByteArrayInputStream(fileByte); InputStream stream = new ByteArrayInputStream(fileByte);
PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(path + "/" + objectName) PutObjectArgs objectArgs = PutObjectArgs.builder().bucket(bucketName).object(path + "/" + objectName)
.stream(stream, fileByte.length, -1).contentType(contentType).build(); .stream(stream, fileByte.length, -1).contentType(contentType).build();
//文件名称相同会覆盖 //文件名称相同会覆盖
minioClient.putObject(objectArgs); minioClient.putObject(objectArgs);
} catch (Exception e) { } catch (Exception e) {
@ -127,7 +120,6 @@ public class MinioService {
return objectName; return objectName;
} }
/** /**
* 删除 * 删除
* *
@ -138,7 +130,8 @@ public class MinioService {
*/ */
public boolean remove(String bucketName, String path, String fileName) { public boolean remove(String bucketName, String path, String fileName) {
try { try {
minioClient.removeObject(RemoveObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build()); minioClient.removeObject(
RemoveObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build());
} catch (Exception e) { } catch (Exception e) {
log.error("删除文件失败,", e); log.error("删除文件失败,", e);
return false; return false;
@ -156,7 +149,7 @@ public class MinioService {
*/ */
public Boolean isExist(String bucketName, String path, String fileName) { public Boolean isExist(String bucketName, String path, String fileName) {
try { try {
minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build()); minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(path + "/" + fileName).build());
} catch (Exception e) { } catch (Exception e) {
return false; return false;
} }

10
im-platform/src/main/java/com/bx/implatform/util/FileUtil.java

@ -13,6 +13,16 @@ public final class FileUtil {
return fileName.substring(fileName.lastIndexOf(".") + 1); return fileName.substring(fileName.lastIndexOf(".") + 1);
} }
/**
* 去除文件扩展名
*
* @param fileName 文件名
* @return
*/
public static String excludeExtension(String fileName) {
return fileName.substring(0,fileName.lastIndexOf("."));
}
/** /**
* 判断文件是否图片类型 * 判断文件是否图片类型
* *

7
im-uniapp/App.vue

@ -116,8 +116,11 @@ export default {
this.chatStore.refreshChats(); this.chatStore.refreshChats();
}).catch((e) => { }).catch((e) => {
console.log(e) console.log(e)
this.$message.error("拉取离线消息失败"); uni.showToast({
this.onExit(); title: "拉取离线消息失败",
icon: "none"
})
this.exit();
}) })
}, },
pullPrivateOfflineMessage(minId) { pullPrivateOfflineMessage(minId) {

35
im-uniapp/store/chatStore.js

@ -335,11 +335,11 @@ export default defineStore('chatStore', {
} }
}, },
refreshChats() { refreshChats() {
if (!cacheChats) return; let chats = cacheChats || this.chats;
// 更新会话免打扰状态 // 更新会话免打扰状态
const friendStore = useFriendStore(); const friendStore = useFriendStore();
const groupStore = useGroupStore(); const groupStore = useGroupStore();
cacheChats.forEach(chat => { chats.forEach(chat => {
if (chat.type == 'PRIVATE') { if (chat.type == 'PRIVATE') {
let friend = friendStore.findFriend(chat.targetId); let friend = friendStore.findFriend(chat.targetId);
if (friend) { if (friend) {
@ -353,28 +353,37 @@ export default defineStore('chatStore', {
} }
}) })
// 排序 // 排序
cacheChats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); chats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime);
// #ifndef APP-PLUS // #ifndef APP-PLUS
/** /**
* 由于h5和小程序的stroge只有5m,大约只能存储2w条消息 * 由于h5和小程序的stroge只有5m,大约只能存储2w条消息所以可能需要清理部分历史消息
* 所以这里每个会话只保留1000条消息防止溢出
*/ */
cacheChats.forEach(chat => { this.fliterMessage(chats, 5000, 1000);
if (chat.messages.length > 1000) {
let idx = chat.messages.length - 1000;
chat.messages = chat.messages.slice(idx);
}
})
// #endif // #endif
// 记录热数据索引位置 // 记录热数据索引位置
cacheChats.forEach(chat => chat.hotMinIdx = chat.messages.length); chats.forEach(chat => chat.hotMinIdx = chat.messages.length);
// 将消息一次性装载回来 // 将消息一次性装载回来
this.chats = cacheChats; this.chats = chats;
// 清空缓存,不再使用 // 清空缓存,不再使用
cacheChats = null; cacheChats = null;
// 消息持久化 // 消息持久化
this.saveToStorage(true); this.saveToStorage(true);
}, },
fliterMessage(chats, maxTotalSize, maxPerChatSize) {
// 每个会话只保留maxPerChatSize条消息
let remainTotalSize = 0;
chats.forEach(chat => {
if (chat.messages.length > maxPerChatSize) {
let idx = chat.messages.length - maxPerChatSize;
chat.messages = chat.messages.slice(idx);
}
remainTotalSize += chat.messages.length;
})
// 保证消息总数不超过maxTotalSize条,否则继续清理
if (remainTotalSize > maxTotalSize) {
this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2);
}
},
saveToStorage(withColdMessage) { saveToStorage(withColdMessage) {
// 加载中不保存,防止卡顿 // 加载中不保存,防止卡顿
if (this.loading) { if (this.loading) {

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

@ -26,9 +26,11 @@ export default {
}, },
methods: { methods: {
open(pos, items) { open(pos, items) {
this.pos = pos; this.pos.x = pos.x;
this.pos.y = pos.y;
this.items = items; this.items = items;
this.show = true; this.show = true;
this.rejustPos();
}, },
close() { close() {
this.show = false; this.show = false;
@ -36,6 +38,16 @@ export default {
onSelectMenu(item) { onSelectMenu(item) {
this.$emit("select", item); this.$emit("select", item);
this.close(); this.close();
},
rejustPos() {
let menuH = this.items.length * 36;
let menuW = 100;
if (this.pos.y > window.innerHeight - menuH) {
this.pos.y = window.innerHeight - menuH;
}
if (this.pos.x > window.innerWidth - menuW) {
this.pos.x = window.innerWidth - menuW;
}
} }
} }
} }
@ -75,4 +87,4 @@ export default {
} }
} }
} }
</style> </style>

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

@ -333,11 +333,11 @@ export default defineStore('chatStore', {
} }
}, },
refreshChats() { refreshChats() {
if (!cacheChats) return; let chats = cacheChats || this.chats;
// 刷新免打扰状态 // 刷新免打扰状态
const friendStore = useFriendStore(); const friendStore = useFriendStore();
const groupStore = useGroupStore(); const groupStore = useGroupStore();
cacheChats.forEach(chat => { chats.forEach(chat => {
if (chat.type == 'PRIVATE') { if (chat.type == 'PRIVATE') {
let friend = friendStore.findFriend(chat.targetId); let friend = friendStore.findFriend(chat.targetId);
if (friend) { if (friend) {
@ -351,26 +351,38 @@ export default defineStore('chatStore', {
} }
}) })
// 排序 // 排序
cacheChats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime); chats.sort((chat1, chat2) => chat2.lastSendTime - chat1.lastSendTime);
/** /**
* 由于部分浏览器不支持websql或indexdb只能使用localstorage而localstorage大小只有10m,可能会导致缓存空间溢出 * 由于部分浏览器不支持websql或indexdb只能使用localstorage而localstorage大小只有10m,可能会导致缓存空间溢出
* 解决办法:如果是使用localstorage的浏览器每个会话只保留1000条消息防止溢出 * 解决办法:针对只能使用localstorage的浏览器最多保留1w条消息,每个会话最多保留1000条消息
*/ */
cacheChats.forEach(chat => { if (localForage.driver().includes("localStorage")) {
if (localForage.driver().includes("localStorage") && chat.messages.length > 1000) { this.fliterMessage(chats, 10000, 1000)
let idx = chat.messages.length - 1000; }
chat.messages = chat.messages.slice(idx);
}
})
// 记录热数据索引位置 // 记录热数据索引位置
cacheChats.forEach(chat => chat.hotMinIdx = chat.messages.length); chats.forEach(chat => chat.hotMinIdx = chat.messages.length);
// 将消息一次性装载回来 // 将消息一次性装载回来
this.chats = cacheChats; this.chats = chats;
// 清空缓存 // 清空缓存
cacheChats = null; cacheChats = null;
// 持久化消息 // 持久化消息
this.saveToStorage(true); this.saveToStorage(true);
}, },
fliterMessage(chats, maxTotalSize, maxPerChatSize) {
// 每个会话只保留maxPerChatSize条消息
let remainTotalSize = 0;
chats.forEach(chat => {
if (chat.messages.length > maxPerChatSize) {
let idx = chat.messages.length - maxPerChatSize;
chat.messages = chat.messages.slice(idx);
}
remainTotalSize += chat.messages.length;
})
// 保证消息总数不超过maxTotalSize条,否则继续清理
if (remainTotalSize > maxTotalSize) {
this.fliterMessage(chats, maxTotalSize, maxPerChatSize / 2);
}
},
saveToStorage(withColdMessage) { saveToStorage(withColdMessage) {
// 加载中不保存,防止卡顿 // 加载中不保存,防止卡顿
if (this.loading) { if (this.loading) {

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

@ -1,5 +1,8 @@
<template> <template>
<div class="login-view"> <div class="login-view">
<div class="decoration decoration-1"></div>
<div class="decoration decoration-2"></div>
<div class="decoration decoration-3"></div>
<div class="content"> <div class="content">
<el-form class="form" :model="loginForm" status-icon :rules="rules" ref="loginForm" <el-form class="form" :model="loginForm" status-icon :rules="rules" ref="loginForm"
@keyup.enter.native="submitForm('loginForm')"> @keyup.enter.native="submitForm('loginForm')">
@ -121,10 +124,67 @@ export default {
.login-view { .login-view {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: #E8F2FF;
background-size: cover;
box-sizing: border-box; box-sizing: border-box;
background: linear-gradient(135deg,
#87adeb 0%,
#8287ec 25%,
#87adeb 50%,
#898ee3 75%,
#87adeb 100%);
/* 装饰性元素 */
.decoration {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
}
.decoration-1 {
width: 450px;
height: 450px;
background: linear-gradient(45deg, rgba(255, 255, 255, 0.3) 0%, rgba(200, 220, 240, 0.4) 100%);
top: -250px;
right: -100px;
animation: float 16s infinite ease-in-out;
}
.decoration-2 {
width: 400px;
height: 400px;
background: linear-gradient(135deg, rgba(200, 220, 240, 0.4) 0%, rgba(255, 255, 255, 0.3) 100%);
bottom: -200px;
left: -100px;
animation: float 12s infinite ease-in-out;
}
.decoration-3 {
width: 300px;
height: 300px;
background: linear-gradient(45deg, rgba(161, 196, 253, 0.3) 0%, rgba(194, 233, 251, 0.4) 100%);
top: 50%;
right: 50px;
animation: float 8s infinite ease-in-out;
}
@keyframes float {
0%,
100% {
transform: translateY(0) translateX(0);
}
25% {
transform: translateY(-60px) translateX(30px);
}
50% {
transform: translateY(30px) translateX(-45px);
}
75% {
transform: translateY(-30px) translateX(-30px);
}
}
.content { .content {
position: relative; position: relative;
@ -134,15 +194,14 @@ export default {
padding: 10%; padding: 10%;
.form { .form {
height: 340px; width: 360px;
width: 400px; height: 380px;
padding: 30px; padding: 30px;
background: white; box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
opacity: 0.9; background: rgba(255, 255, 255, 0.85);
box-shadow: 0px 0px 1px #ccc; backdrop-filter: blur(8px);
border-radius: 3%; border-radius: 3%;
overflow: hidden; overflow: hidden;
border: 1px solid #ccc;
.title { .title {
display: flex; display: flex;

133
im-web/src/view/Register.vue

@ -1,37 +1,37 @@
<template> <template>
<el-container class="register-view"> <el-container class="register-view">
<div> <div class="decoration decoration-1"></div>
<el-form :model="registerForm" status-icon :rules="rules" ref="registerForm" label-width="80px" <div class="decoration decoration-2"></div>
class="content"> <div class="decoration decoration-3"></div>
<div class="title"> <el-form :model="registerForm" status-icon :rules="rules" ref="registerForm" label-width="80px" class="content">
<img class="logo" src="../../public/logo.png" /> <div class="title">
<div>欢迎成为盒子IM的用户</div> <img class="logo" src="../../public/logo.png" />
</div> <div>欢迎成为盒子IM的用户</div>
<el-form-item label="用户名" prop="userName"> </div>
<el-input type="userName" v-model="registerForm.userName" autocomplete="off" placeholder="用户名(登录使用)" <el-form-item label="用户名" prop="userName">
maxlength="20"></el-input> <el-input type="userName" v-model="registerForm.userName" autocomplete="off" placeholder="用户名(登录使用)"
</el-form-item> maxlength="20"></el-input>
<el-form-item label="昵称" prop="nickName"> </el-form-item>
<el-input type="nickName" v-model="registerForm.nickName" autocomplete="off" placeholder="昵称" <el-form-item label="昵称" prop="nickName">
maxlength="20"></el-input> <el-input type="nickName" v-model="registerForm.nickName" autocomplete="off" placeholder="昵称"
</el-form-item> maxlength="20"></el-input>
<el-form-item label="密码" prop="password"> </el-form-item>
<el-input type="password" v-model="registerForm.password" autocomplete="off" placeholder="密码" <el-form-item label="密码" prop="password">
maxlength="20"></el-input> <el-input type="password" v-model="registerForm.password" autocomplete="off" placeholder="密码"
</el-form-item> maxlength="20"></el-input>
<el-form-item label="确认密码" prop="confirmPassword"> </el-form-item>
<el-input type="password" v-model="registerForm.confirmPassword" autocomplete="off" <el-form-item label="确认密码" prop="confirmPassword">
placeholder="确认密码" maxlength="20"></el-input> <el-input type="password" v-model="registerForm.confirmPassword" autocomplete="off" placeholder="确认密码"
</el-form-item> maxlength="20"></el-input>
<el-form-item> </el-form-item>
<el-button type="primary" @click="submitForm('registerForm')">注册</el-button> <el-form-item>
<el-button @click="resetForm('registerForm')">清空</el-button> <el-button type="primary" @click="submitForm('registerForm')">注册</el-button>
</el-form-item> <el-button @click="resetForm('registerForm')">清空</el-button>
<div class="to-login"> </el-form-item>
<router-link to="/login">已有账号,前往登录</router-link> <div class="to-login">
</div> <router-link to="/login">已有账号,前往登录</router-link>
</el-form> </div>
</div> </el-form>
<icp></icp> <icp></icp>
</el-container> </el-container>
</template> </template>
@ -131,18 +131,77 @@ export default {
align-items: center; align-items: center;
width: 100%; width: 100%;
height: 100%; height: 100%;
background: rgb(232, 242, 255); background: linear-gradient(135deg,
#87adeb 0%,
#8287ec 25%,
#87adeb 50%,
#898ee3 75%,
#87adeb 100%);
/* 装饰性元素 */
.decoration {
position: absolute;
border-radius: 50%;
background: rgba(255, 255, 255, 0.2);
}
.decoration-1 {
width: 450px;
height: 450px;
background: linear-gradient(45deg, rgba(255, 255, 255, 0.3) 0%, rgba(200, 220, 240, 0.4) 100%);
top: -250px;
right: -100px;
animation: float 16s infinite ease-in-out;
}
.decoration-2 {
width: 400px;
height: 400px;
background: linear-gradient(135deg, rgba(200, 220, 240, 0.4) 0%, rgba(255, 255, 255, 0.3) 100%);
bottom: -200px;
left: -100px;
animation: float 12s infinite ease-in-out;
}
.decoration-3 {
width: 300px;
height: 300px;
background: linear-gradient(45deg, rgba(161, 196, 253, 0.3) 0%, rgba(194, 233, 251, 0.5) 100%);
top: 50%;
right: 50px;
animation: float 8s infinite ease-in-out;
}
@keyframes float {
0%,
100% {
transform: translateY(0) translateX(0);
}
25% {
transform: translateY(-60px) translateX(30px);
}
50% {
transform: translateY(30px) translateX(-45px);
}
75% {
transform: translateY(-30px) translateX(-30px);
}
}
.content { .content {
width: 500px; width: 400px;
height: 450px; height: 450px;
padding: 20px; padding: 20px;
background: white;
opacity: 0.9;
box-shadow: 0px 0px 1px #ccc;
border-radius: 3px; border-radius: 3px;
overflow: hidden; overflow: hidden;
border-radius: 3%; border-radius: 3%;
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
background: rgba(255, 255, 255, 0.85);
backdrop-filter: blur(8px);
.title { .title {
display: flex; display: flex;

Loading…
Cancel
Save