勿妄 1 month ago
commit af1e136e3a

@ -9,6 +9,7 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"@vue/shared": "^3.5.13", "@vue/shared": "^3.5.13",
"axios": "^1.8.3", "axios": "^1.8.3",
"element-plus": "^2.9.7", "element-plus": "^2.9.7",

@ -8,6 +8,9 @@ importers:
.: .:
dependencies: dependencies:
'@element-plus/icons-vue':
specifier: ^2.3.1
version: 2.3.1(vue@3.5.13(typescript@5.7.3))
'@vue/shared': '@vue/shared':
specifier: ^3.5.13 specifier: ^3.5.13
version: 3.5.13 version: 3.5.13

@ -137,7 +137,7 @@ const onLoginSubmit = () => {
if(login_password_email.value == testEmail.value && login_password.value == testPassword.value) { if(login_password_email.value == testEmail.value && login_password.value == testPassword.value) {
console.log('测试登录成功') console.log('测试登录成功')
console.log(router) console.log(router)
router.push({name:'Personal'}) router.push({name:'Home'})
} else { } else {
login().then((res) => { login().then((res) => {
if(res.code === 200) { if(res.code === 200) {
@ -314,7 +314,7 @@ async function login(){
padding: 0; padding: 0;
} }
body{ :global(body){
height: 100vh;; height: 100vh;;
/*弹性布局,水平垂直居中*/ /*弹性布局,水平垂直居中*/
display:flex; display:flex;

@ -1,17 +1,527 @@
<script set lang="ts"> <script set lang="ts">
import { defineComponent } from 'vue'; import { defineComponent, ref } from 'vue';
export default defineComponent({ export default defineComponent({
name: 'Manager', name: 'Manager',
setup() {
const email = ref('test@example.com');
const username = ref('测试员');
return{
email,
username
}
}
}); });
</script> </script>
<template> <template>
<h1>测试样例</h1> <div class = "container">
<p> <div class="main-content">
测试样例2 <!-- 左侧信息设置区 -->
</p> <div class="card profile-info-card">
<!-- 个人信息部分 -->
<div class="form-section">
<h2 class="section-title">个人信息</h2>
<div class="form-group">
<label class="form-label">昵称</label>
<input type="text" class="form-input" :placeholder="username" readonly>
</div>
<div class="form-group">
<label class="form-label">个人简介</label>
<input type="text" class="form-input" placeholder="这是一个个人简介">
</div>
<div class="form-group">
<label class="form-label">性别</label>
<div class="radio-group">
<label class="radio-label">
<input type="radio" name="gender" class="radio-input" checked>
<span></span>
</label>
<label class="radio-label">
<input type="radio" name="gender" class="radio-input">
<span></span>
</label>
</div>
</div>
<div class="form-group">
<label class="form-label">生日</label>
<input type="date" class="date-input">
</div>
</div>
<div class="divider"></div>
<!-- 账号信息部分 -->
<div class="form-section">
<h2 class="section-title">账号信息</h2>
<div class="form-group">
<label class="form-label">绑定邮箱</label>
<input type="email" class="form-input" :placeholder="email" readonly>
</div>
<div class="form-group">
<label class="form-label">修改密码</label>
<div class="password-inputs">
<input type="password" class="form-input" placeholder="xxxxxx旧密码">
<input type="password" class="form-input" placeholder="xxxxxx新密码">
</div>
<button class="btn btn-primary btn-password">确认修改</button>
</div>
<p class="form-hint">* 密码至少包含6个字符建议使用字母数字和符号的组合</p>
</div>
<div class="divider"></div>
<!-- 保存按钮 -->
<div class="btn-save-container">
<button class="btn btn-primary"> </button>
</div>
</div>
<!-- 右侧预览区 -->
<div class="card profile-preview-card">
<div class="preview-section">
<div class="preview-avatar">
<img src="../../../public/images/默认头像.jpg" alt="用户头像">
</div>
<h3>{{ username }}</h3>
<p class="preview-email">{{ email }}</p>
<div class="preview-bio">
这是一个个人简介
</div>
</div>
<!-- 可爱装饰元素 -->
<div class="cute-decoration star-1"></div>
<div class="cute-decoration star-2"></div>
<div class="cute-decoration heart">💜</div>
<div class="cute-decoration cat">🐱</div>
<div class="cute-decoration cake">🍰</div>
</div>
</div>
</div>
</template> </template>
<sytle scoped> <style scoped>
</sytle>
#app {
width: 100%;
max-width: none;
padding: 0;
margin: 0;
}
/* 主容器 */
.container {
position:absolute;
background-color:transparent;
left:100px;
top:2%;
width:94%;
height: 96%;
}
/* 侧边栏 */
/* 用户头像容器 */
.avatar-container {
display: flex;
justify-content: center;
margin: 20px 0 30px;
}
.avatar-circle {
width: 90px;
height: 90px;
border-radius: 50%;
background-color: #fff;
display: flex;
justify-content: center;
align-items: center;
border: 3px solid #fff;
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.1);
overflow: hidden;
transition: transform 0.3s ease;
}
.avatar-circle:hover {
transform: scale(1.05);
}
.avatar-circle img {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 主内容区 */
.main-content {
flex: 1;
height:90%;
padding: 30px;
display: flex;
gap: 20px;
}
/* 信息卡片通用样式 */
.card {
background-color: #fff;
border-radius: 20px;
padding: 30px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card:hover {
transform: translateY(-5px);
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.1);
}
/* 左侧内容卡片 */
.profile-info-card {
flex: 2;
}
/* 右侧内容卡片 */
.profile-preview-card {
padding-top:70px;
flex: 1;
}
/* 分隔线 */
.divider {
height: 1px;
background-color: #e6e6fa;
margin: 25px 0;
border-radius: 1px;
}
/* 表单区 */
.form-section {
margin-bottom: 30px;
}
.section-title {
font-size: 2rem;
color: #9370DB;
margin-bottom: 20px;
font-weight: 500;
}
.form-group {
height:60px;
margin-bottom: 15px;
margin-left:10px;
display: flex;
align-items: center;
}
.form-label {
width: 95px;
font-size: 1.25rem;
color: #666;
text-align:left;
}
/* 输入框样式 */
.form-input {
flex: 1;
height:30px;
padding: 10px 15px;
border: 2px solid #e6e6fa;
border-radius: 25px;
outline: none;
transition: border-color 0.3s ease;
font-size: 1.2rem;
}
.form-input:focus {
border-color: #b19cd9;
}
/* 单选按钮样式 */
.radio-group {
transform:scale(1.2);
display: flex;
gap: 30px;
}
.radio-label {
display: flex;
align-items: center;
cursor: pointer;
}
.radio-input {
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
border: 2px solid #e6e6fa;
border-radius: 50%;
margin-right: 8px;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.2s ease;
}
.radio-input:checked {
border-color: #9370DB;
}
.radio-input:checked::before {
content: "";
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #9370DB;
}
/* 日期选择器 */
.date-input {
width: 100%;
padding: 10px 15px;
border: 2px solid #e6e6fa;
border-radius: 25px;
outline: none;
transition: border-color 0.3s ease;
background-color: #fff;
font-size:1.2rem;
}
.date-input:focus {
border-color: #b19cd9;
}
/* 按钮样式 */
.btn {
outline:none;
padding: 10px 24px;
border: none;
border-radius: 25px;
cursor: pointer;
font-size: 1rem;
font-weight: 500;
transition: all 0.3s ease;
background-color: #fff;
}
.btn-primary {
background-color: #9370DB;
color: white;
box-shadow: 0 4px 10px rgba(147, 112, 219, 0.3);
}
.btn-primary:hover {
background-color: #8a63d2;
transform: translateY(-2px);
box-shadow: 0 6px 12px rgba(147, 112, 219, 0.4);
}
.btn-save-container {
display: flex;
justify-content: center;
margin-top: 20px;
}
/* 预览区样式 */
.preview-section {
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
}
.preview-avatar {
width: 150px;
height: 150px;
border-radius: 50%;
border: 5px solid #fff;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
overflow: hidden;
margin-bottom: 10px;
}
.preview-avatar img {
width: 100%;
height: 100%;
object-fit: cover;
}
.preview-email {
color: #666;
font-size: 1.2rem;
margin-bottom: 15px;
margin-top:0px;
}
.preview-bio {
width: 100%;
min-height: 100px;
padding: 15px;
border: 2px solid #e6e6fa;
border-radius: 15px;
background-color: #f9f7ff;
font-size: 1.2rem;
color: #666;
}
/* 密码修改区域样式 */
.password-inputs {
display: flex;
gap: 10px;
margin-right: 10px;
}
.password-inputs .form-input {
flex: 1;
}
/* 响应式设计 */
@media (max-width: 1024px) {
.main-content {
flex-direction: column;
}
}
@media (max-width: 768px) {
.container {
flex-direction: column;
}
.sidebar {
width: 100%;
padding: 10px;
}
.menu-item {
border-radius: 25px;
margin-right: 0;
padding: 10px 15px;
text-align: center;
}
.main-content {
padding: 15px;
}
.form-group {
flex-direction: column;
align-items: flex-start;
}
.form-label {
width: 100%;
margin-bottom: 5px;
}
.password-inputs {
flex-direction: column;
}
}
/* 可爱元素:气泡背景 */
.bubble {
position: absolute;
border-radius: 50%;
background-color: rgba(255, 255, 255, 0.3);
z-index: -1;
animation: float 15s ease-in-out infinite;
}
.bubble-1 {
width: 100px;
height: 100px;
bottom: 50px;
left: 20px;
animation-delay: 0s;
}
.bubble-2 {
width: 60px;
height: 60px;
top: 100px;
left: 40px;
animation-delay: 2s;
}
.bubble-3 {
width: 80px;
height: 80px;
bottom: 200px;
left: 60px;
animation-delay: 5s;
}
@keyframes float {
0%, 100% {
transform: translateY(0);
}
50% {
transform: translateY(-20px);
}
}
/* 动画效果 */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeIn 0.5s ease forwards;
}
.profile-preview-card {
animation-delay: 0.2s;
}
/* 提示文本样式 */
.form-hint {
font-size: 0.8rem;
color: #9370DB;
margin-top: 5px;
margin-left: 100px;
}
/* 可爱装饰元素 */
.cute-decoration {
position: absolute;
font-size: 1.5rem;
opacity: 0.3;
color: #9370DB;
pointer-events: none;
}
.star-1 {
top: 50px;
right: 20px;
}
.star-2 {
bottom: 100px;
right: 40px;
}
.heart {
bottom: 30px;
right: 30px;
}
</style>

@ -99,17 +99,17 @@ export default defineComponent({
justify-content:center; justify-content:center;
align-items:center; align-items:center;
font:900 100px ''; font:900 100px '';
color:rgba(110,90,240,0.3); color:rgba(175, 90, 240, 0.308);
background-color: #e4e9f5; background-color: #e4e9f5;
} }
.shell{ .shell{
position:absolute; position:fixed;
top:0%; top:0%;
left:0%; left:0%;
width:84px; width:84px;
height:100%; height:100%;
background-color:#fff; background-color:#ead1fb;
z-index:9999; z-index:9999;
transition:width 0.5s; transition:width 0.5s;
padding-left:10px; padding-left:10px;
@ -145,7 +145,7 @@ export default defineComponent({
} }
.active{ .active{
background-color: #e4e9f5; background-color: #fff;
border-top-left-radius: 50px; border-top-left-radius: 50px;
border-bottom-left-radius: 50px; border-bottom-left-radius: 50px;
} }
@ -158,7 +158,7 @@ export default defineComponent({
width:30px; width:30px;
height:30px; height:30px;
border-bottom-right-radius:25px; border-bottom-right-radius:25px;
box-shadow:5px 5px 0 5px #e4e9f5; box-shadow:5px 5px 0 5px #fff;
background:transparent; background:transparent;
} }
@ -170,7 +170,7 @@ export default defineComponent({
width:30px; width:30px;
height:30px; height:30px;
border-top-right-radius: 25px; border-top-right-radius: 25px;
box-shadow:5px -5px 0 5px #e4e9f5; box-shadow:5px -5px 0 5px #fff;
background:transparent; background:transparent;
} }
@ -194,7 +194,7 @@ export default defineComponent({
height:70px; height:70px;
color:#333; color:#333;
transition:0.5s; transition:0.5s;
color: rgb(110,90,240) color: rgb(153, 109, 240)
} }
.icon i{ .icon i{
@ -218,7 +218,7 @@ export default defineComponent({
.shell ul li:hover a .icon, .shell ul li:hover a .icon,
.shell ul li:hover a .text .shell ul li:hover a .text
{ {
color: #ffa117;/*字体和图标被选中后的颜色 */ color: #f3e7e9;/*字体和图标被选中后的颜色 */
} }
.active a .icon::before{ .active a .icon::before{

@ -52,4 +52,11 @@ pnpm add element-plus
pnpm i vue-router pnpm i vue-router
pnpm add vue-router@4 pnpm add vue-router@4
``` ```
用来完成界面跳转同时完成vuerouter与ts的适配 用来完成界面跳转同时完成vuerouter与ts的适配
### 安装elementplus的icon库
```
pnpm install @element-plus/icons-vue
```

@ -1,3 +1,5 @@
[TOC]
## 一、API规范 ## 一、API规范
### 1.1 基础信息 ### 1.1 基础信息
@ -66,8 +68,8 @@
| id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 用户ID | | id | BIGINT | PRIMARY KEY, AUTO_INCREMENT | 用户ID |
| username | VARCHAR(50) | NOT NULL, UNIQUE | 用户名 | | username | VARCHAR(50) | NOT NULL, UNIQUE | 用户名 |
| email | VARCHAR(100) | NOT NULL, UNIQUE | 邮箱地址(学校邮箱) | | email | VARCHAR(100) | NOT NULL, UNIQUE | 邮箱地址(学校邮箱) |
| password | VARCHAR(255) | NOT NULL | 密码(加密存储 | | password | VARCHAR(255) | NOT NULL | 密码(加密存储 |
| nickname | VARCHAR(50) | NOT NULL | 昵称 | | | | | |
| avatar | VARCHAR(255) | | 头像URL | | avatar | VARCHAR(255) | | 头像URL |
| bio | TEXT | | 个人简介 | | bio | TEXT | | 个人简介 |
| gender | TINYINT | | 性别0-未知, 1-男, 2-女) | | gender | TINYINT | | 性别0-未知, 1-男, 2-女) |
@ -256,3 +258,151 @@ CREATE TABLE `users` (
} }
``` ```
### 3.2用户信息管理模块
#### 3.2.1获取用户个人信息
#### 请求信息
- **URL**: `/users/profile`
- **方法**: `GET`
- **描述**: 获取当前登录用户的个人资料信息
#### 响应结果
```json
Copy{
"code": 200,
"message": "success",
"data": {
"id": 12345,
"username": "student123",
"email": "student@school.edu",
"nickname": "测试员",
"avatar": "https://example.com/avatars/default.png",
"bio": "这是一个个人简介",
"gender": 2,
"studentId": "20220101001",
"department": "计算机学院",
"major": "软件工程",
"grade": "2023级",
"points": 100,
"role": 0,
"isVerified": 1
}
}
```
#### 3.2.2 更新用户个人信息
#### 请求信息
- **URL**: `/users/profile`
- **方法**: `PUT`
- **描述**: 更新当前登录用户的个人资料
#### 请求参数
```json
Copy{
"nickname": "新昵称",
"bio": "这是更新后的个人简介",
"gender": 2,
}
```
#### 响应结果
```json
Copy{
"code": 200,
"message": "更新成功",
"data": null
}
```
#### 3.2.3 修改用户密码
#### 请求信息
- **URL**: `/users/password`
- **方法**: `PUT`
- **描述**: 修改当前登录用户的密码
#### 请求参数
```json
Copy{
"code": "验证码",
"newPassword": "新密码",
}
```
#### 响应结果
```json
Copy{
"code": 200,
"message": "密码修改成功",
"data": null
}
```
#### 3.2.4 上传用户头像
#### 请求信息
- **URL**: `/users/avatar`
- **方法**: `POST`
- **描述**: 上传或更新用户头像
#### 请求参数
```
Copy
file: [图片文件]
```
#### 响应结果
```json
Copy{
"code": 200,
"message": "头像上传成功",
"data": {
"avatar": "https://example.com/avatars/user_123456.jpg"
}
}
```
### 2.5 更新用户邮箱
#### 请求信息
- **URL**: `/users/email`
- **方法**: `PUT`
- **描述**: 更新用户邮箱地址
#### 请求参数
```json
Copy{
"email": "new_email@school.edu",
"code": "123456" // 邮箱验证码
}
```
#### 响应结果
```json
Copy{
"code": 200,
"message": "邮箱更新成功",
"data": null
}
```

@ -6,7 +6,6 @@ import com.unilife.model.dto.LoginDTO;
import com.unilife.model.dto.LoginEmailDTO; import com.unilife.model.dto.LoginEmailDTO;
import com.unilife.model.dto.RegisterDTO; import com.unilife.model.dto.RegisterDTO;
import com.unilife.model.vo.LoginVO; import com.unilife.model.vo.LoginVO;
import com.unilife.model.vo.RegisterVO;
import com.unilife.service.UserService; import com.unilife.service.UserService;
import com.unilife.utils.BaseContext; import com.unilife.utils.BaseContext;
import com.unilife.utils.JwtUtil; import com.unilife.utils.JwtUtil;
@ -14,14 +13,12 @@ import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
@Api(tags = "用户管理") @Api(tags = "用户管理")
@ -34,19 +31,17 @@ public class UserController {
private UserService userService; private UserService userService;
@Autowired @Autowired
private JwtUtil jwtUtil; private JwtUtil jwtUtil;
@Autowired
private StringRedisTemplate stringRedisTemplate;
@ApiOperation(value = "用户注册") @ApiOperation(value = "用户注册")
@PostMapping("register") @PostMapping("register")
public Result register(@RequestBody RegisterDTO registerDTO, HttpServletRequest request) { public Result<?> register(@RequestBody RegisterDTO registerDTO, HttpServletRequest request) {
return userService.register(registerDTO,request); return userService.register(registerDTO,request);
} }
@ApiOperation(value = "用户登录") @ApiOperation(value = "用户登录")
@PostMapping("login") @PostMapping("login")
public Result login(@RequestBody LoginDTO loginDTO,HttpServletRequest request) { public Result<?> login(@RequestBody LoginDTO loginDTO,HttpServletRequest request) {
Result login = userService.login(loginDTO,request); Result<?> login = userService.login(loginDTO,request);
//登陆成功后生成jwt令牌 //登陆成功后生成jwt令牌
LoginVO vo=(LoginVO) login.getData(); LoginVO vo=(LoginVO) login.getData();
if (vo == null) { if (vo == null) {
@ -63,7 +58,7 @@ public class UserController {
@ApiOperation(value = "获取邮箱验证码") @ApiOperation(value = "获取邮箱验证码")
@PostMapping("code") @PostMapping("code")
public Result getCode(@RequestBody EmailDTO emailDto,HttpServletRequest request) { public Result<?> getCode(@RequestBody EmailDTO emailDto,HttpServletRequest request) {
String email=emailDto.getEmail(); String email=emailDto.getEmail();
log.debug("收到的原始邮箱: {}", email); log.debug("收到的原始邮箱: {}", email);
return userService.sendVerificationCode(email,request); return userService.sendVerificationCode(email,request);
@ -71,7 +66,7 @@ public class UserController {
@ApiOperation(value = "邮箱验证码登录") @ApiOperation(value = "邮箱验证码登录")
@PostMapping("login/code") @PostMapping("login/code")
public Result loginWithEmailCode(@RequestBody LoginEmailDTO loginEmailDTO,HttpServletRequest request) { public Result<?> loginWithEmailCode(@RequestBody LoginEmailDTO loginEmailDTO,HttpServletRequest request) {
return userService.loginWithEmail(loginEmailDTO,request); return userService.loginWithEmail(loginEmailDTO,request);
} }

Loading…
Cancel
Save