用户信息显示完成,管理端入口实现,但是登陆后个人中心页面和发布帖子页面刷新会报错,需要解决

main
哆哆咯哆哆咯 2 months ago
parent 0efc812afb
commit 538276c248

@ -17,6 +17,7 @@ import io.minio.http.Method;
import io.minio.messages.DeleteError;
import io.minio.messages.DeleteObject;
import io.minio.messages.Item;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
@ -51,21 +52,58 @@ public class FileServiceImpl extends ServiceImpl<LjFileMapper, LjFile> implement
//线程池
private static final ExecutorService SAVE_TO_DB_EXECUTOR = Executors.newFixedThreadPool(10);
@PostConstruct
private void init(){
createBucket("videos");
createBucket("images");
createBucket("chunks");
try {
createBucketWithPolicy("videos");
createBucketWithPolicy("images");
createBucketWithPolicy("chunks");
log.info("MinIO桶初始化完成");
} catch (Exception e) {
log.error("MinIO桶初始化失败: {}", e.getMessage(), e);
}
}
/**
*
*/
private void createBucketWithPolicy(String name) throws Exception {
boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
if (!isExist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
log.info("创建桶: {}", name);
// 设置桶策略为公共读取
String readOnlyPolicy = """
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"AWS": ["*"]},
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::%s/*"]
}
]
}
""".formatted(name);
minioClient.setBucketPolicy(
SetBucketPolicyArgs.builder()
.bucket(name)
.config(readOnlyPolicy)
.build()
);
log.info("设置桶{}的公共读取策略成功", name);
}
}
@Override
public Boolean createBucket(String name){
try {
boolean isExist = minioClient.
bucketExists(BucketExistsArgs.builder().bucket(name).build());
if (!isExist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build());
}
createBucketWithPolicy(name);
} catch (Exception e) {
log.error("创建桶失败: {}", e.getMessage(), e);
throw new FileException("创建桶失败");
}
return true;

@ -1,9 +1,8 @@
package com.luojia_channel.modules.user.controller;
import com.luojia_channel.common.domain.Result;
import com.luojia_channel.modules.file.dto.UploadFileDTO;
import com.luojia_channel.modules.user.dto.UserChangeInfoDTO;
import com.luojia_channel.modules.user.dto.UserInfoDTO;
import com.luojia_channel.modules.user.vo.UserInfoVO;
import com.luojia_channel.modules.user.service.UserInfoService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -30,7 +29,7 @@ public class UserInfoController {
@ApiResponse(responseCode = "200", description = "获取成功"),
@ApiResponse(responseCode = "500", description = "获取失败,用户不存在或未登录")
})
public Result<UserInfoDTO> getUserInfo(@RequestParam(required = false) Long userId) {
public Result<UserInfoVO> getUserInfo(@RequestParam(required = false) Long userId) {
return Result.success(userInfoService.getUserInfo(userId));
}

@ -7,6 +7,7 @@ import com.luojia_channel.modules.user.utils.CaptchaUtils;
import com.luojia_channel.modules.user.dto.UserLoginDTO;
import com.luojia_channel.modules.user.dto.UserRegisterDTO;
import com.luojia_channel.modules.user.service.UserLoginService;
import com.luojia_channel.modules.user.vo.UserInfoVO;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
@ -34,6 +35,7 @@ import java.util.concurrent.TimeUnit;
public class UserLoginController {
private final UserLoginService userLoginService;
private final RedisUtil redisUtil;
@PostMapping("/login")
@Operation(
summary = "用户登录",
@ -41,13 +43,31 @@ public class UserLoginController {
tags = {"用户管理"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "登录成功",content = @Content(schema = @Schema(implementation = UserDTO.class))),
@ApiResponse(responseCode = "200", description = "登录成功",content = @Content(schema = @Schema(implementation = UserInfoVO.class))),
@ApiResponse(responseCode = "500", description = "登录失败,用户名或密码错误")
})
public Result<UserDTO> login(@RequestBody UserLoginDTO userLoginDTO){
public Result<UserInfoVO> login(@RequestBody UserLoginDTO userLoginDTO){
return Result.success(userLoginService.login(userLoginDTO));
}
@PostMapping("/check-login")
@Operation(
summary = "检查用户登录状态",
description = "通过accessToken和refreshToken验证用户登录状态",
tags = {"用户管理"}
)
@ApiResponses(value = {
@ApiResponse(responseCode = "200", description = "验证成功"),
@ApiResponse(responseCode = "500", description = "验证失败token无效或已过期")
})
public Result<UserDTO> checkLogin(@RequestHeader(value = "Authorization", required = false) String accessToken,
@RequestHeader(value = "X-Refresh-Token", required = false) String refreshToken) {
if (accessToken != null && accessToken.startsWith("Bearer ")) {
accessToken = accessToken.substring(7);
}
return Result.success(userLoginService.checkLogin(accessToken, refreshToken));
}
@PostMapping("/register")
@Operation(
summary = "用户注册",

@ -1,9 +1,8 @@
package com.luojia_channel.modules.user.service;
import com.baomidou.mybatisplus.extension.service.IService;
import com.luojia_channel.modules.file.dto.UploadFileDTO;
import com.luojia_channel.modules.user.dto.UserChangeInfoDTO;
import com.luojia_channel.modules.user.dto.UserInfoDTO;
import com.luojia_channel.modules.user.vo.UserInfoVO;
import com.luojia_channel.modules.user.entity.User;
import org.springframework.web.multipart.MultipartFile;
@ -20,5 +19,5 @@ public interface UserInfoService extends IService<User> {
* @param userId IDnull
* @return DTO
*/
UserInfoDTO getUserInfo(Long userId);
UserInfoVO getUserInfo(Long userId);
}

@ -5,11 +5,12 @@ import com.luojia_channel.common.domain.UserDTO;
import com.luojia_channel.modules.user.dto.UserLoginDTO;
import com.luojia_channel.modules.user.dto.UserRegisterDTO;
import com.luojia_channel.modules.user.entity.User;
import com.luojia_channel.modules.user.vo.UserInfoVO;
import jakarta.servlet.http.HttpServletRequest;
public interface UserLoginService extends IService<User> {
UserDTO login(UserLoginDTO userLoginDTO);
UserInfoVO login(UserLoginDTO userLoginDTO);
UserDTO checkLogin(String accessToken, String refreshToken);

@ -5,12 +5,9 @@ import cn.hutool.crypto.digest.BCrypt;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luojia_channel.common.exception.UserException;
import com.luojia_channel.common.utils.UserContext;
import com.luojia_channel.modules.file.dto.UploadFileDTO;
import com.luojia_channel.modules.file.service.impl.FileServiceImpl;
import com.luojia_channel.modules.file.utils.GeneratePathUtil;
import com.luojia_channel.modules.file.utils.ValidateFileUtil;
import com.luojia_channel.modules.user.dto.UserChangeInfoDTO;
import com.luojia_channel.modules.user.dto.UserInfoDTO;
import com.luojia_channel.modules.user.vo.UserInfoVO;
import com.luojia_channel.modules.user.entity.User;
import com.luojia_channel.modules.user.mapper.UserMapper;
import com.luojia_channel.modules.user.service.UserInfoService;
@ -68,7 +65,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserMapper, User> implement
}
@Override
public UserInfoDTO getUserInfo(Long userId) {
public UserInfoVO getUserInfo(Long userId) {
// 如果userId为null则获取当前登录用户的ID
if (userId == null) {
userId = UserContext.getUserId();
@ -84,7 +81,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserMapper, User> implement
}
// 转换为DTO对象
UserInfoDTO userInfoDTO = UserInfoDTO.builder()
UserInfoVO userInfoVO = UserInfoVO.builder()
.id(user.getId())
.username(user.getUsername())
.phone(user.getPhone())
@ -96,6 +93,6 @@ public class UserInfoServiceImpl extends ServiceImpl<UserMapper, User> implement
.status(user.getStatus())
.build();
return userInfoDTO;
return userInfoVO;
}
}

@ -17,6 +17,7 @@ import com.luojia_channel.modules.user.service.UserLoginService;
import com.luojia_channel.common.utils.JWTUtil;
import com.luojia_channel.common.utils.RedisUtil;
import com.luojia_channel.modules.user.utils.ValidateUserUtil;
import com.luojia_channel.modules.user.vo.UserInfoVO;
import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@ -69,16 +70,24 @@ public class UserLoginServiceImpl extends ServiceImpl<UserMapper, User> implemen
/**
* token
* @param userDTO
* @param userId ID
* @param username
* @return accessrefresh token
*/
private void generateTokens(UserDTO userDTO){
private String[] generateTokens(Long userId, String username){
UserDTO userDTO = UserDTO.builder()
.userId(userId)
.username(username)
.build();
String accessToken = jwtUtil.generateAccessToken(userDTO);
String refreshToken = jwtUtil.generateRefreshToken(userDTO);
userDTO.setAccessToken(accessToken);
userDTO.setRefreshToken(refreshToken);
//存储refreshToken到redis
String key = "refresh_token:" + userDTO.getUserId();
String key = REFRESH_TOKEN_PREFIX + userId;
redisUtil.set(key, refreshToken, EXPIRE_TIME, TimeUnit.DAYS);
return new String[]{accessToken, refreshToken};
}
/**
@ -88,19 +97,33 @@ public class UserLoginServiceImpl extends ServiceImpl<UserMapper, User> implemen
* @return
*/
@Override
public UserDTO login(UserLoginDTO userLoginDTO) {
public UserInfoVO login(UserLoginDTO userLoginDTO) {
String userFlag = userLoginDTO.getUserFlag();
String password = userLoginDTO.getPassword();
User user = getUserByFlag(userFlag);
if (!BCrypt.checkpw(password, user.getPassword())) {
throw new UserException("密码错误");
}
UserDTO userDTO = UserDTO.builder()
.userId(user.getId())
// 生成token
String[] tokens = generateTokens(user.getId(), user.getUsername());
// 构建返回的用户信息
UserInfoVO userInfoVO = UserInfoVO.builder()
.id(user.getId())
.username(user.getUsername())
.phone(user.getPhone())
.email(user.getEmail())
.avatar(user.getAvatar())
.gender(user.getGender())
.college(user.getCollege())
.role(user.getRole())
.status(user.getStatus())
.accessToken(tokens[0])
.refreshToken(tokens[1])
.build();
generateTokens(userDTO);
return userDTO;
return userInfoVO;
}
/**
@ -157,7 +180,13 @@ public class UserLoginServiceImpl extends ServiceImpl<UserMapper, User> implemen
.userId(user.getId())
.username(user.getUsername())
.build();
generateTokens(userDTO);
String accessToken = jwtUtil.generateAccessToken(userDTO);
String refreshToken = jwtUtil.generateRefreshToken(userDTO);
userDTO.setAccessToken(accessToken);
userDTO.setRefreshToken(refreshToken);
//存储refreshToken到redis
String key = "refresh_token:" + userDTO.getUserId();
redisUtil.set(key, refreshToken, EXPIRE_TIME, TimeUnit.DAYS);
return userDTO;
}
}

@ -1,4 +1,4 @@
package com.luojia_channel.modules.user.dto;
package com.luojia_channel.modules.user.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
@ -10,8 +10,8 @@ import lombok.NoArgsConstructor;
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "用户信息DTO")
public class UserInfoDTO {
@Schema(description = "用户信息VO")
public class UserInfoVO {
@Schema(
description = "用户ID"
)
@ -56,4 +56,14 @@ public class UserInfoDTO {
description = "状态(1正常2冻结)"
)
private Integer status;
@Schema(
description = "访问令牌"
)
private String accessToken;
@Schema(
description = "刷新令牌"
)
private String refreshToken;
}

@ -14,8 +14,8 @@
password: 123456
minio:
endpoint: http://localhost:9000
accessKey: minioadmin
secretKey: minioadmin
accessKey: root
secretKey: 12345678
#lj:
# db:

@ -65,5 +65,8 @@ mybatis-plus:
mapper-locations: classpath*:mapper/**/*.xml
type-aliases-package: com.luojia.luojia_channel.modules.*.entity
-management:
- health:
- elasticsearch:
- enabled: false

@ -43,7 +43,6 @@
<img :src="userInfo.avatar || defaultAvatar" alt="用户头像" class="avatar-img" />
<!-- 悬浮板块 -->
<div class="user-dropdown-menu" v-show="isDropdownVisible">
<p class="user-name">{{ userInfo.username }}</p>
<div class="button-container">
<button @click="goToProfile" class="dropdown-btn">个人中心</button>
<router-link to="/ChangeInformation" class="dropdown-btn">修改个人信息</router-link>
@ -379,45 +378,38 @@ watch(() => userStore.userInfo, (newInfo) => {
z-index: 1000;
display: flex;
flex-direction: column;
align-items: center; /* 水平居中用户名 */
align-items: center; /* 水平居中 */
text-align: center; /* 确保文字居中 */
gap: 10px; /* 增加子元素之间的间距 */
width: 200px; /* 固定宽度,确保布局一致 */
}
/* 用户名样式 */
.user-name {
font-size: 20px;
font-weight: bold;
margin-bottom: 10px;
color: #54ac52;
width: 140px; /* 减小宽度 */
}
/* 按钮容器样式 */
.button-container {
display: flex;
flex-direction: column; /* 改为垂直排列 */
gap: 8px; /* 按钮间间距 */
width: 100%; /* 按钮容器占满父容器宽度 */
width: 100%; /* 确保按钮宽度一致 */
}
/* 悬浮板块按钮样式 */
/* 下拉菜单按钮样式 */
.dropdown-btn {
width: 100%; /* 按钮占据父容器的100%宽度 */
padding: 8px 10px;
background: none;
border: 0;
padding: 8px 0;
background-color: transparent;
border: none;
border-radius: 4px;
text-align: center;
font-size: 14px;
color: #333;
font-size: 14px;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
text-decoration: none; /* 去除router-link的下划线 */
transition: background-color 0.2s, color 0.2s;
text-decoration: none;
display: block;
width: 100%;
text-align: center;
}
.dropdown-btn:hover {
background-color: #f0f0f0;
color: #6fbd87;
background-color: #f0f5ff;
color: #54ac52;
}
.admin-btn {

@ -209,16 +209,14 @@ async function login() {
const response = await request.post('/user/login', loginData);
if (response.code === 200) {
// token
const { accessToken, refreshToken } = response.data;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
userStore.login({
avatar:require ('@/assets/default-avatar/boy_1.png'),
userName: '珈人一号',
userid:1
});
//
const userData = response.data;
// ID
userData.userName = userData.username;
userData.userid = userData.id;
userStore.login(userData);
ElMessage({
message: '登录成功',

@ -4,10 +4,48 @@ import router from './router'; // 确保引入了 router
import { createPinia } from 'pinia';
import ELementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import { checkLoginStatus } from './utils/auth';
import { useUserStore } from './stores/user';
const app = createApp(App);
const pinia = createPinia(); // 创建 Pinia 实例
app.use(pinia); // 注册 Pinia
// 尝试从localStorage恢复用户状态立即执行不等待API
const userStore = useUserStore();
const userId = localStorage.getItem('userId');
const username = localStorage.getItem('username');
const avatar = localStorage.getItem('avatar');
const role = localStorage.getItem('role');
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
// 如果本地存储中有用户信息,立即恢复登录状态
if (userId && username && accessToken && refreshToken) {
userStore.login({
userid: userId,
userName: username,
avatar: avatar || '',
role: role || 1,
accessToken,
refreshToken
});
console.log('从本地存储恢复用户登录状态:', username);
}
app.use(router); // 注册 vue-router
app.use(ELementPlus); // 注册 element-plus
app.mount('#app');
// 挂载应用
app.mount('#app');
// 在应用启动后异步验证登录状态,不阻塞应用启动
setTimeout(() => {
checkLoginStatus()
.then(isLoggedIn => {
console.log('登录状态验证完成,用户登录状态:', isLoggedIn ? '已登录' : '未登录');
})
.catch(error => {
console.error('登录状态验证失败,但不影响应用使用:', error);
});
}, 500);

@ -8,6 +8,9 @@ import NotificationList from '@/views/NotificationList.vue';
import ChangeInformation from '@/views/ChangeInformation.vue';
import FeedBack from '@/views/FeedBack.vue';
import PostPublish from '@/views/PostPublish.vue';
import { useUserStore } from '@/stores/user.js';
import { checkLoginStatus } from '@/utils/auth';
import { ElMessage } from 'element-plus';
const routes = [
{
@ -33,18 +36,21 @@ const routes = [
{
path: '/user',
name: 'UserPage',
component: UserPage
component: UserPage,
meta: { requiresAuth: true }
},
{//通知页面
path: '/notificationlist',
name: 'NotificationList',
component: NotificationList
component: NotificationList,
meta: { requiresAuth: true }
},
{//详细通知页面
path: '/notification/:id',
name: 'NotificationDetail',
component: () => import('@/views/NotificationDetail.vue'),
props: true
props: true,
meta: { requiresAuth: true }
},
{//反馈页面
path: '/feedback',
@ -55,13 +61,15 @@ const routes = [
//修改个人信息界面
path:'/changeinformation',
name:'ChangeInformation',
component:ChangeInformation
component:ChangeInformation,
meta: { requiresAuth: true }
},
{
// 发布帖子页面
path: '/postpublish',
name: 'PostPublish',
component: PostPublish
component: PostPublish,
meta: { requiresAuth: true }
},
{
// 管理员页面
@ -95,7 +103,7 @@ const routes = [
component: () => import('@/views/admin/AdminCategories.vue')
}
],
meta: { requiresAdmin: true }
meta: { requiresAdmin: true, requiresAuth: true }
}
];
@ -104,4 +112,86 @@ const router = createRouter({
routes
});
// 全局前置守卫
router.beforeEach(async (to, from, next) => {
const userStore = useUserStore();
// 检查路由是否需要登录
if (to.matched.some(record => record.meta.requiresAuth)) {
// 如果用户未登录先尝试从localStorage恢复登录状态
if (!userStore.isLoggedIn) {
console.log('访问需要登录的页面,但用户未登录,尝试恢复登录状态...');
// 尝试从localStorage恢复用户状态
const userId = localStorage.getItem('userId');
const username = localStorage.getItem('username');
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
const avatar = localStorage.getItem('avatar');
const role = localStorage.getItem('role');
// 如果localStorage中有用户信息先恢复登录状态
if (userId && username && accessToken && refreshToken) {
userStore.login({
userid: userId,
userName: username,
avatar: avatar || '',
role: role || 1,
accessToken,
refreshToken
});
console.log('从localStorage恢复用户登录状态:', username);
// 异步验证token有效性但不阻塞导航
setTimeout(() => {
checkLoginStatus().catch(err => {
console.error('Token验证失败但不影响当前导航:', err);
});
}, 100);
// 继续导航
next();
return;
}
// 如果localStorage中没有用户信息尝试通过token验证登录状态
try {
console.log('尝试通过API验证登录状态...');
const isLoggedIn = await checkLoginStatus();
if (!isLoggedIn) {
ElMessage.warning('请先登录');
next({ path: '/' });
return;
}
} catch (error) {
console.error('登录状态验证失败:', error);
ElMessage.warning('登录状态验证失败,请重新登录');
next({ path: '/' });
return;
}
}
}
// 检查路由是否需要管理员权限
if (to.matched.some(record => record.meta.requiresAdmin)) {
// 检查用户是否登录且是否有管理员权限
if (!userStore.isLoggedIn) {
ElMessage.error('请先登录');
next({ path: '/' });
return;
}
// 检查用户是否有管理员权限
const role = parseInt(userStore.userInfo.role || localStorage.getItem('role') || 1);
if (!(role === 2 || role === 3)) {
ElMessage.error('您没有管理员权限');
next({ path: '/' });
return;
}
}
// 继续导航
next();
});
export default router;

@ -9,18 +9,65 @@ export const useUserStore = defineStore('user', {
avatar: '', // 用户头像
email: '', // 用户邮箱
phone: '', // 用户手机号
studentId: '', // 学号
college: '', // 学院
gender: 0, // 性别,0-未知,1-男,2-女
role: 1, // 角色1-普通用户2-管理员3-超级管理员
status: 1, // 状态1-正常2-冻结
},
}),
actions: {
login(userData) {
this.isLoggedIn = true;
this.userInfo = {
...userData,
userid: userData.userid || 0,
username: userData.userName || '',
avatar: userData.avatar || '',
email: userData.email || '',
phone: userData.phone || '',
college: userData.college || '',
gender: userData.gender || 0,
role: userData.role || 1,
status: userData.status || 1
};
// 保存令牌到本地存储(如果有的话)
if (userData.accessToken) {
localStorage.setItem('accessToken', userData.accessToken);
}
if (userData.refreshToken) {
localStorage.setItem('refreshToken', userData.refreshToken);
}
// 保存基本用户信息到localStorage用于页面刷新时恢复
localStorage.setItem('userId', this.userInfo.userid);
localStorage.setItem('username', this.userInfo.username);
localStorage.setItem('avatar', this.userInfo.avatar || '');
localStorage.setItem('role', this.userInfo.role);
console.log('用户登录成功,状态已更新:', this.userInfo);
},
// 更新用户信息
updateUserInfo(userData) {
this.userInfo = {
...this.userInfo,
username: userData.username || this.userInfo.username,
avatar: userData.avatar || this.userInfo.avatar,
email: userData.email || this.userInfo.email,
phone: userData.phone || this.userInfo.phone,
college: userData.college || this.userInfo.college,
gender: userData.gender !== undefined ? userData.gender : this.userInfo.gender,
role: userData.role || this.userInfo.role,
status: userData.status || this.userInfo.status
};
// 更新localStorage中的用户信息
localStorage.setItem('username', this.userInfo.username);
localStorage.setItem('avatar', this.userInfo.avatar || '');
console.log('用户信息已更新:', this.userInfo);
},
logout() {
this.isLoggedIn = false;
this.userInfo = {
@ -29,11 +76,21 @@ export const useUserStore = defineStore('user', {
avatar: '',
email: '',
phone: '',
studentId: '',
college: '',
gender: 0,
isPublic: true
role: 1,
status: 1
};
// 清除本地存储的令牌和用户信息
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('userId');
localStorage.removeItem('username');
localStorage.removeItem('avatar');
localStorage.removeItem('role');
console.log('用户已登出');
},
},
});

@ -0,0 +1,132 @@
import axios from 'axios';
import { useUserStore } from '@/stores/user';
/**
* 检查用户登录状态
* 如果本地存储中有token则尝试验证token有效性
* @returns {Promise<boolean>} 是否登录成功
*/
export async function checkLoginStatus() {
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
// 如果没有token直接返回未登录
if (!accessToken || !refreshToken) {
console.log('本地没有找到token无需验证登录状态');
return false;
}
// 检查token格式是否正确
if (!isValidToken(accessToken) || !isValidToken(refreshToken)) {
console.log('Token格式不正确清除无效token');
clearAuthTokens();
return false;
}
try {
console.log('正在验证登录状态...');
// 使用axios直接发送请求避免request拦截器中可能的循环依赖
const response = await axios.post('/user/check-login', null, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-Refresh-Token': refreshToken
},
// 设置超时时间,避免长时间等待
timeout: 5000
});
if (response.data && response.data.code === 200 && response.data.data) {
console.log('登录状态有效,用户信息:', response.data.data);
// 更新用户状态
const userStore = useUserStore();
userStore.login({
userid: response.data.data.userId,
userName: response.data.data.username,
avatar: response.data.data.avatar,
role: response.data.data.role,
accessToken: response.data.data.accessToken || accessToken,
refreshToken: response.data.data.refreshToken || refreshToken
});
// 如果返回了新的token更新本地存储
if (response.data.data.accessToken) {
localStorage.setItem('accessToken', response.data.data.accessToken);
}
if (response.data.data.refreshToken) {
localStorage.setItem('refreshToken', response.data.data.refreshToken);
}
return true;
} else {
console.log('登录状态已过期或无效');
// 如果后端明确表示token无效则清除token
if (response.data && response.data.code === 401) {
clearAuthTokens();
}
return false;
}
} catch (error) {
console.error('验证登录状态失败:', error);
// 如果是网络错误、超时或服务器未响应保留token并尝试从localStorage恢复
if (!error.response || error.code === 'ECONNABORTED') {
console.log('网络错误或服务器未响应尝试从localStorage恢复登录状态');
// 如果localStorage中有用户信息则维持登录状态
const userId = localStorage.getItem('userId');
const username = localStorage.getItem('username');
const avatar = localStorage.getItem('avatar');
const role = localStorage.getItem('role');
if (userId && username) {
const userStore = useUserStore();
userStore.login({
userid: userId,
userName: username,
avatar: avatar || '',
role: role || 1,
accessToken: accessToken,
refreshToken: refreshToken
});
console.log('从localStorage成功恢复用户登录状态:', username);
return true;
}
return false;
}
// 只有在确认token格式错误时才清除
if (error.response && error.response.status === 500 &&
error.response.data?.message?.includes('token')) {
console.log('Token格式错误清除token');
clearAuthTokens();
}
return false;
}
}
/**
* 检查token格式是否有效
* @param {string} token 要检查的token
* @returns {boolean} 是否有效
*/
function isValidToken(token) {
// 简单检查token是否为字符串且长度合理
return typeof token === 'string' && token.length > 10;
}
/**
* 清除认证令牌
*/
export function clearAuthTokens() {
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
localStorage.removeItem('userId');
localStorage.removeItem('username');
localStorage.removeItem('avatar');
localStorage.removeItem('role');
const userStore = useUserStore();
userStore.logout();
}

@ -1,9 +1,43 @@
import axios from 'axios';
import { clearAuthTokens } from './auth';
import { ElMessage } from 'element-plus';
const request = axios.create({
timeout: 5000
});
// 是否正在刷新token
let isRefreshing = false;
// 等待刷新token的请求队列
let requestsQueue = [];
// 执行队列中的请求
const processQueue = (error, token = null) => {
requestsQueue.forEach(prom => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
requestsQueue = [];
};
// 判断是否是需要登录才能访问的API
const isAuthRequiredApi = (url) => {
// 这些API需要登录才能访问如果未登录会返回401
const authRequiredApis = [
'/user/info',
'/post/publish',
'/notification',
'/user/info/avatar',
'/user/info/update',
'/user/info/password'
];
return authRequiredApis.some(api => url.includes(api));
};
//响应拦截器
request.interceptors.response.use(
response => {
@ -14,6 +48,106 @@ request.interceptors.response.use(
},
error => {
console.error('请求错误:', error);
// 如果响应状态码是401或500可能是token过期或格式错误
if (error.response && (error.response.status === 401 ||
(error.response.status === 500 && error.response.data?.message?.includes('token')))) {
const originalRequest = error.config;
// 判断是否是需要登录的API
const needAuth = isAuthRequiredApi(originalRequest.url);
// 如果是500错误且与token相关直接清除token并拒绝请求
if (error.response.status === 500 && error.response.data?.message?.includes('token')) {
console.error('Token格式错误:', error.response.data.message);
clearAuthTokens();
if (needAuth) {
ElMessage.warning('登录已失效,请重新登录');
}
return Promise.reject(error);
}
// 防止重复刷新token
if (!isRefreshing) {
isRefreshing = true;
// 尝试使用refreshToken获取新的accessToken
const refreshToken = localStorage.getItem('refreshToken');
const accessToken = localStorage.getItem('accessToken');
if (!refreshToken || !accessToken || typeof refreshToken !== 'string' || typeof accessToken !== 'string') {
if (needAuth) {
ElMessage.warning('请先登录');
}
// 只有当token格式明显不正确时才清除
if ((refreshToken && typeof refreshToken !== 'string') ||
(accessToken && typeof accessToken !== 'string')) {
clearAuthTokens();
}
isRefreshing = false;
return Promise.reject(error);
}
// 调用check-login接口刷新token
return axios.post('/user/check-login', null, {
headers: {
'Authorization': `Bearer ${accessToken}`,
'X-Refresh-Token': refreshToken
}
}).then(res => {
if (res.data && res.data.code === 200 && res.data.data) {
// 更新token
const { accessToken, refreshToken } = res.data.data;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
// 更新原始请求的Authorization头
originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
originalRequest.headers['X-Refresh-Token'] = refreshToken;
// 处理队列中的请求
processQueue(null, accessToken);
// 重新发送原始请求
return axios(originalRequest);
} else {
processQueue(new Error('刷新Token失败'), null);
if (needAuth) {
// 不清除token只提示用户
ElMessage.warning('请先登录');
}
return Promise.reject(error);
}
}).catch(err => {
processQueue(err, null);
if (needAuth) {
// 只有在确认token格式错误时才清除
if (err.response && err.response.status === 500 &&
err.response.data?.message?.includes('token')) {
clearAuthTokens();
}
ElMessage.warning('请先登录');
}
return Promise.reject(err);
}).finally(() => {
isRefreshing = false;
});
} else {
// 将请求加入队列
return new Promise((resolve, reject) => {
requestsQueue.push({
resolve: token => {
originalRequest.headers['Authorization'] = `Bearer ${token}`;
resolve(axios(originalRequest));
},
reject: err => {
reject(err);
}
});
});
}
}
return Promise.reject(error);
}
);
@ -21,13 +155,31 @@ request.interceptors.response.use(
//请求拦截器
request.interceptors.request.use(
config => {
console.log('Request:',config);
if (!config.url.includes('/captcha') && !config.url.includes('/verify-captcha')) {
console.log('Request:', config.url);
// 只对非认证相关的API添加token
if (!config.url.includes('/captcha') && !config.url.includes('/verify-captcha') &&
!config.url.includes('/user/login') && !config.url.includes('/user/register')) {
const token = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
if (token) {
// 检查token格式是否正确
if (token && refreshToken && typeof token === 'string' && typeof refreshToken === 'string') {
config.headers['Authorization'] = `Bearer ${token}`;
config.headers['X-Refresh-Token'] = refreshToken;
config.headers['X-Refresh-Token'] = refreshToken;
console.log('添加token到请求头:', config.url);
} else if ((token && typeof token !== 'string') ||
(refreshToken && typeof refreshToken !== 'string')) {
// 只有当token格式明显错误时才清除
console.warn('Token格式不正确清除token');
clearAuthTokens();
} else if (!token || !refreshToken) {
// 如果没有token但访问需要认证的API尝试从localStorage恢复
const userId = localStorage.getItem('userId');
const username = localStorage.getItem('username');
if (userId && username) {
console.warn('访问需要认证的API但没有token可能需要重新登录');
}
}
}
return config;

@ -6,7 +6,12 @@
<div class="avatar-section">
<p class="section-title">头像</p>
<div class="avatar-preview">
<img :src="currentAvatar" alt="当前头像" class="current-avatar">
<img
:src="currentAvatar || defaultAvatar"
:onerror="handleAvatarError"
alt="当前头像"
class="current-avatar"
>
<img v-if="previewAvatar" :src="previewAvatar" alt="新头像预览" class="new-avatar">
</div>
<label class="upload-btn">
@ -134,7 +139,7 @@
</template>
<script setup>
import { ref, computed } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useUserStore } from '@/stores/user';
import request from '@/utils/request';
import { ElMessage } from 'element-plus';
@ -142,24 +147,81 @@ import { useRouter } from 'vue-router'
const userStore = useUserStore();
const userInfo = computed(() => userStore.userInfo);
const router = useRouter();
//
const defaultAvatar = require('@/assets/default-avatar/boy_4.png');
//
const handleAvatarError = "this.onerror=null;this.src='" + defaultAvatar + "'";
//
const formData = ref({
username: userInfo.value.username || '',
phone: userInfo.value.phone || '',
email: userInfo.value.email || '',
studentId: userInfo.value.studentId || '',
gender: userInfo.value.gender !== undefined ? String(userInfo.value.gender) : '0',
college: userInfo.value.college || '',
moto: userInfo.value.moto || '',
password: userInfo.value.password || ''
username: '',
phone: '',
email: '',
studentId: '',
gender: '0',
college: '',
moto: '',
password: ''
});
const avatarFile = ref(null);
const previewAvatar = ref('');
const currentAvatar = ref(userInfo.value.avatar);
const currentAvatar = ref('');
const errors = ref({});
const router=useRouter();
const loading = ref(false);
//
const loadUserInfo = async () => {
loading.value = true;
try {
// store
formData.value.username = userStore.userInfo.username || '';
currentAvatar.value = userStore.userInfo.avatar || '';
// localStorage
if (!formData.value.username) {
formData.value.username = localStorage.getItem('username') || '';
currentAvatar.value = localStorage.getItem('avatar') || '';
}
// API
const response = await request.get('/user/info/getuserinfo', {
params: { userId: userStore.userInfo.userid || localStorage.getItem('userId') }
});
if (response && response.code === 200 && response.data) {
//
formData.value = {
username: response.data.username || formData.value.username,
phone: response.data.phone || '',
email: response.data.email || '',
studentId: response.data.studentId || '',
gender: response.data.gender !== undefined ? String(response.data.gender) : '0',
college: response.data.college || '',
moto: response.data.moto || '',
password: '' // API
};
//
if (response.data.avatar) {
currentAvatar.value = response.data.avatar;
}
console.log('成功加载用户信息:', formData.value);
} else {
console.warn('获取用户信息失败或返回数据为空');
}
} catch (error) {
console.error('加载用户信息失败:', error);
ElMessage.warning('获取用户信息失败,请稍后再试');
} finally {
loading.value = false;
}
};
//
const handleAvatarChange = (e) => {
@ -251,15 +313,20 @@ const handleSubmit = async () => {
if (avatarFile.value) {
const formDataObj = new FormData();
formDataObj.append('file', avatarFile.value);
console.log('正在上传头像...');
const uploadRes = await request.post('/user/info/avatar', formDataObj, {
headers: { 'Content-Type': 'multipart/form-data' }
});
if (uploadRes.code !== 200) {
console.log('头像上传响应:', uploadRes);
if (uploadRes.code !== 200 || !uploadRes.data) {
throw new Error(uploadRes.msg || '头像上传失败');
}
avatarUrl = uploadRes.data; // URL
avatarUrl = uploadRes.data;
console.log('获取到的头像URL:', avatarUrl);
currentAvatar.value = avatarUrl;
userInfo.value.avatar = avatarUrl; //
}
//
@ -268,16 +335,25 @@ const handleSubmit = async () => {
phone: formData.value.phone,
email: formData.value.email,
studentId: formData.value.studentId,
avatar: userInfo.value.avatar,
avatar: avatarUrl, // 使URL
gender: Number(formData.value.gender),
college: formData.value.college,
};
console.log('提交的用户信息:', updateInfoData);
const updateInfoRes = await request.post('/user/info/update', updateInfoData);
if (updateInfoRes.code !== 200) {
throw new Error(updateInfoRes.msg || '修改用户信息失败');
}
Object.assign(userInfo.value, updateInfoData);
//
userStore.updateUserInfo({
...updateInfoData,
userid: userInfo.value.userid,
role: userInfo.value.role,
status: userInfo.value.status
});
if (formData.value.password) {
const passwordRes = await request.post('/user/info/password', null, {
@ -288,14 +364,19 @@ const handleSubmit = async () => {
}
}
ElMessage.success('修改成功!');
router.push('/');
} catch (error) {
console.error('修改失败:', error);
ElMessage.error(`修改失败:${error.message}`);
}
};
//
onMounted(() => {
loadUserInfo();
});
</script>
<style scoped>

@ -94,11 +94,13 @@
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import axios from 'axios';
import { Plus } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import request from '@/utils/request';
import { useUserStore } from '@/stores/user';
const router = useRouter();
const userStore = useUserStore();
const postFormRef = ref(null);
//
@ -133,12 +135,44 @@ const submitResult = reactive({
title: ''
});
//
const checkLoginStatus = () => {
//
if (!userStore.isLoggedIn) {
// localStorage
const userId = localStorage.getItem('userId');
const username = localStorage.getItem('username');
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
const avatar = localStorage.getItem('avatar');
const role = localStorage.getItem('role');
if (userId && username && accessToken && refreshToken) {
userStore.login({
userid: userId,
userName: username,
avatar: avatar || '',
role: role || 1,
accessToken,
refreshToken
});
console.log('从localStorage恢复用户登录状态:', username);
return true;
} else {
ElMessage.warning('请先登录');
router.push('/');
return false;
}
}
return true;
};
//
const loadCategories = async () => {
try {
const response = await axios.get('/api/categories');
if (response.data.code === 0) {
categories.value = response.data.data || [];
const response = await request.get('/post/category');
if (response && response.code === 200) {
categories.value = response.data || [];
}
} catch (error) {
console.error('加载分类失败:', error);
@ -161,23 +195,24 @@ const handleRemove = (file) => {
//
const submitForm = async () => {
if (!postFormRef.value) return;
if (!checkLoginStatus() || !postFormRef.value) return;
try {
submitting.value = true;
await postFormRef.value.validate();
const response = await axios.post('/api/post', form);
const response = await request.post('/post/publish', form);
if (response.data.code === 0) {
if (response && response.code === 200) {
showResult('success', '帖子发布成功');
setTimeout(() => {
router.push(`/post/${response.data.data}`);
router.push(`/post/${response.data}`);
}, 1500);
} else {
showResult('error', response.data.msg || '发布失败');
showResult('error', response.msg || '发布失败');
}
} catch (error) {
console.error('发布失败:', error);
showResult('error', error.response?.data?.msg || '网络错误');
} finally {
submitting.value = false;
@ -186,18 +221,21 @@ const submitForm = async () => {
// 稿
const saveDraft = async () => {
if (!checkLoginStatus()) return;
try {
const response = await axios.post('/api/drafts', {
const response = await request.post('/post/draft', {
...form,
status: 1 // 1稿
});
if (response.data.code === 0) {
if (response && response.code === 200) {
showResult('success', '草稿保存成功');
} else {
showResult('error', response.data.msg || '保存失败');
showResult('error', response.msg || '保存失败');
}
} catch (error) {
console.error('保存草稿失败:', error);
showResult('error', error.response?.data?.msg || '网络错误');
}
};
@ -213,9 +251,11 @@ const showResult = (type, message) => {
}, 3000);
};
//
//
onMounted(() => {
loadCategories();
if (checkLoginStatus()) {
loadCategories();
}
});
</script>

@ -3,7 +3,12 @@
<div class="left-container">
<div class="user-info-card">
<div class="user-avatar">
<img :src="userInfo.avatar" alt="用户头像" />
<img
:src="userInfo.avatar || defaultAvatar"
:onerror="handleAvatarError"
alt="用户头像"
class="avatar"
/>
</div>
<div class="user-details">
<h2 class="username">{{ userInfo.username }}</h2>
@ -13,8 +18,16 @@
<span class="info-icon"><i class="el-icon-school"></i></span>
<span class="info-label">学院</span>
<span class="info-value">{{ userInfo.college || '未设置' }}</span>
<el-switch
v-if="isCurrentUser"
v-model="privacySettings.showCollege"
active-text="公开"
inactive-text="隐藏"
@change="updatePrivacy"
class="privacy-switch"
/>
</div>
<div class="user-info-item">
<div class="user-info-item" v-if="isCurrentUser || privacySettings.showGender">
<span class="info-icon"><i class="el-icon-user"></i></span>
<span class="info-label">性别</span>
<span class="info-value">
@ -22,43 +35,40 @@
<span v-else-if="userInfo.gender === 2"></span>
<span v-else></span>
</span>
<el-switch
v-if="isCurrentUser"
v-model="privacySettings.showGender"
active-text="公开"
inactive-text="隐藏"
@change="updatePrivacy"
class="privacy-switch"
/>
</div>
<div class="user-info-item" v-if="userInfo.phone && (isCurrentUser || privacySettings.showPhone)">
<span class="info-icon"><i class="el-icon-phone"></i></span>
<span class="info-label">电话</span>
<span class="info-value">{{ userInfo.phone || '未设置' }}</span>
<el-switch
v-if="isCurrentUser"
v-model="privacySettings.showPhone"
active-text="公开"
inactive-text="隐藏"
@change="updatePrivacy"
class="privacy-switch"
/>
</div>
<div class="user-info-item" v-if="userInfo.email && (isCurrentUser || privacySettings.showEmail)">
<span class="info-icon"><i class="el-icon-message"></i></span>
<span class="info-label">邮箱</span>
<span class="info-value">{{ userInfo.email || '未设置' }}</span>
</div>
<div class="user-info-item" v-if="isCurrentUser">
<span class="info-icon"><i class="el-icon-lock"></i></span>
<span class="info-label">隐私设置</span>
<div class="privacy-toggles">
<el-switch
v-model="privacySettings.showPhone"
active-text="电话"
inactive-text="电话"
@change="updatePrivacy"
class="privacy-switch"
/>
<el-switch
v-model="privacySettings.showEmail"
active-text="邮箱"
inactive-text="邮箱"
@change="updatePrivacy"
class="privacy-switch"
/>
<el-switch
v-model="privacySettings.showCollege"
active-text="学院"
inactive-text="学院"
@change="updatePrivacy"
class="privacy-switch"
/>
</div>
<el-switch
v-if="isCurrentUser"
v-model="privacySettings.showEmail"
active-text="公开"
inactive-text="隐藏"
@change="updatePrivacy"
class="privacy-switch"
/>
</div>
</div>
@ -155,7 +165,8 @@
import { ref, reactive, computed, onMounted, onUnmounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useUserStore } from '@/stores/user.js';
import axios from 'axios';
import request from '@/utils/request';
import { ElMessage } from 'element-plus';
const router = useRouter();
const route = useRoute();
@ -192,18 +203,49 @@ const isFollowing = ref(false);
const privacySettings = ref({
showPhone: true,
showEmail: true,
showCollege: true
showCollege: true,
showGender: true
});
//
const recommendations = ref([]);
//
const defaultAvatar = require('@/assets/default-avatar/boy_4.png');
//
const handleAvatarError = "this.onerror=null;this.src='" + defaultAvatar + "'";
//
const loadUserInfo = async () => {
try {
const response = await axios.get(`/api/user/detail?id=${userId.value}`);
if (response.data.code === 0) {
userInfo.value = response.data.data;
// localStorage
if (isCurrentUser.value) {
const storedUsername = localStorage.getItem('username');
const storedAvatar = localStorage.getItem('avatar');
if (storedUsername) {
userInfo.value = {
username: storedUsername,
avatar: storedAvatar || '',
role: localStorage.getItem('role') || 1,
userId: localStorage.getItem('userId')
};
console.log('从localStorage恢复用户基本信息:', storedUsername);
}
}
// 使API
const response = await request.get('/user/info/getuserinfo', {
params: { userId: userId.value },
//
timeout: 5000
});
console.log('获取用户信息响应:', response);
if (response && response.code === 200) {
userInfo.value = response.data;
console.log('加载到的用户信息:', userInfo.value);
//
if (isCurrentUser.value) {
@ -212,6 +254,22 @@ const loadUserInfo = async () => {
}
} catch (error) {
console.error('加载用户信息失败:', error);
// localStorage使
if ((!error.response || error.code === 'ECONNABORTED') && userInfo.value.username) {
console.log('网络错误,但已恢复基本用户信息,继续使用');
// 使
} else if (isCurrentUser.value) {
// userStore
userInfo.value = {
username: userStore.userInfo.username || '用户',
avatar: userStore.userInfo.avatar || '',
userId: userStore.userInfo.userid
};
} else {
//
ElMessage.warning('加载用户信息失败,请稍后再试');
}
}
};
@ -221,15 +279,15 @@ const loadUserPosts = async () => {
loading.value = true;
try {
const response = await axios.get('/api/post/user', {
const response = await request.get('/post/user', {
params: {
userId: userId.value,
...pageParams
}
});
if (response.data.code === 0) {
const newPosts = response.data.data.list || [];
if (response && response.code === 200) {
const newPosts = response.data.list || [];
userPosts.value = [...userPosts.value, ...newPosts];
if (newPosts.length < pageParams.size) {
@ -255,9 +313,9 @@ const loadMorePosts = () => {
//
const loadUserStats = async () => {
try {
const response = await axios.get(`/api/user/stats?id=${userId.value}`);
if (response.data.code === 0) {
userStats.value = response.data.data;
const response = await request.get('/user/stats', { params: { id: userId.value } });
if (response && response.code === 200) {
userStats.value = response.data;
}
} catch (error) {
console.error('加载用户统计数据失败:', error);
@ -268,9 +326,9 @@ const loadUserStats = async () => {
const checkFollowStatus = async () => {
if (!isCurrentUser.value && userStore.isLoggedIn) {
try {
const response = await axios.get(`/api/follow/check?followUserId=${userId.value}`);
if (response.data.code === 0) {
isFollowing.value = response.data.data;
const response = await request.get('/follow/check', { params: { followUserId: userId.value } });
if (response && response.code === 200) {
isFollowing.value = response.data;
}
} catch (error) {
console.error('检查关注状态失败:', error);
@ -287,12 +345,12 @@ const toggleFollow = async () => {
try {
const url = isFollowing.value
? `/api/follow/cancel?followUserId=${userId.value}`
: `/api/follow/add?followUserId=${userId.value}`;
? '/follow/cancel'
: '/follow/add';
const response = await axios.post(url);
const response = await request.post(url, { followUserId: userId.value });
if (response.data.code === 0) {
if (response && response.code === 200) {
isFollowing.value = !isFollowing.value;
//
loadUserStats();
@ -311,9 +369,9 @@ const openChat = () => {
//
const loadPrivacySettings = async () => {
try {
const response = await axios.get('/api/user/privacy');
if (response.data.code === 0) {
privacySettings.value = response.data.data;
const response = await request.get('/user/privacy');
if (response && response.code === 200) {
privacySettings.value = response.data;
}
} catch (error) {
console.error('加载隐私设置失败:', error);
@ -323,7 +381,7 @@ const loadPrivacySettings = async () => {
//
const updatePrivacy = async () => {
try {
await axios.post('/api/user/privacy', privacySettings.value);
await request.post('/user/privacy', privacySettings.value);
} catch (error) {
console.error('更新隐私设置失败:', error);
}
@ -334,12 +392,10 @@ const loadRecommendations = async () => {
if (!userStore.isLoggedIn) return;
try {
const response = await axios.get('/api/user/recommend', {
params: { limit: 5 }
});
const response = await request.get('/user/recommend', { params: { limit: 5 } });
if (response.data.code === 0) {
recommendations.value = response.data.data || [];
if (response && response.code === 200) {
recommendations.value = response.data || [];
}
} catch (error) {
console.error('加载推荐失败:', error);
@ -493,15 +549,8 @@ onUnmounted(() => {
color: #303133;
}
.privacy-toggles {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 5px;
}
.privacy-switch {
margin-right: 15px;
margin-left: 10px;
}
.social-actions {

@ -91,7 +91,7 @@ const checkPermission = () => {
return;
}
if (userStore.userInfo.role < 2) {
if (!(userStore.userInfo.role === 2 || userStore.userInfo.role === 3)) {
ElMessage.error('您没有管理员权限');
router.push('/');
return;

Loading…
Cancel
Save