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

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.DeleteError;
import io.minio.messages.DeleteObject; import io.minio.messages.DeleteObject;
import io.minio.messages.Item; import io.minio.messages.Item;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; 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); private static final ExecutorService SAVE_TO_DB_EXECUTOR = Executors.newFixedThreadPool(10);
@PostConstruct
private void init(){ private void init(){
createBucket("videos"); try {
createBucket("images"); createBucketWithPolicy("videos");
createBucket("chunks"); createBucketWithPolicy("images");
createBucketWithPolicy("chunks");
log.info("MinIO桶初始化完成");
} catch (Exception e) {
log.error("MinIO桶初始化失败: {}", e.getMessage(), e);
}
} }
@Override /**
public Boolean createBucket(String name){ *
try { */
boolean isExist = minioClient. private void createBucketWithPolicy(String name) throws Exception {
bucketExists(BucketExistsArgs.builder().bucket(name).build()); boolean isExist = minioClient.bucketExists(BucketExistsArgs.builder().bucket(name).build());
if (!isExist) { if (!isExist) {
minioClient.makeBucket(MakeBucketArgs.builder().bucket(name).build()); 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 {
createBucketWithPolicy(name);
} catch (Exception e) { } catch (Exception e) {
log.error("创建桶失败: {}", e.getMessage(), e);
throw new FileException("创建桶失败"); throw new FileException("创建桶失败");
} }
return true; return true;

@ -1,9 +1,8 @@
package com.luojia_channel.modules.user.controller; package com.luojia_channel.modules.user.controller;
import com.luojia_channel.common.domain.Result; 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.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 com.luojia_channel.modules.user.service.UserInfoService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
@ -30,7 +29,7 @@ public class UserInfoController {
@ApiResponse(responseCode = "200", description = "获取成功"), @ApiResponse(responseCode = "200", description = "获取成功"),
@ApiResponse(responseCode = "500", 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)); 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.UserLoginDTO;
import com.luojia_channel.modules.user.dto.UserRegisterDTO; import com.luojia_channel.modules.user.dto.UserRegisterDTO;
import com.luojia_channel.modules.user.service.UserLoginService; 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.Hidden;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.media.Content;
@ -34,6 +35,7 @@ import java.util.concurrent.TimeUnit;
public class UserLoginController { public class UserLoginController {
private final UserLoginService userLoginService; private final UserLoginService userLoginService;
private final RedisUtil redisUtil; private final RedisUtil redisUtil;
@PostMapping("/login") @PostMapping("/login")
@Operation( @Operation(
summary = "用户登录", summary = "用户登录",
@ -41,13 +43,31 @@ public class UserLoginController {
tags = {"用户管理"} tags = {"用户管理"}
) )
@ApiResponses(value = { @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 = "登录失败,用户名或密码错误") @ApiResponse(responseCode = "500", description = "登录失败,用户名或密码错误")
}) })
public Result<UserDTO> login(@RequestBody UserLoginDTO userLoginDTO){ public Result<UserInfoVO> login(@RequestBody UserLoginDTO userLoginDTO){
return Result.success(userLoginService.login(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") @PostMapping("/register")
@Operation( @Operation(
summary = "用户注册", summary = "用户注册",

@ -1,9 +1,8 @@
package com.luojia_channel.modules.user.service; package com.luojia_channel.modules.user.service;
import com.baomidou.mybatisplus.extension.service.IService; 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.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.entity.User;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
@ -20,5 +19,5 @@ public interface UserInfoService extends IService<User> {
* @param userId IDnull * @param userId IDnull
* @return DTO * @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.UserLoginDTO;
import com.luojia_channel.modules.user.dto.UserRegisterDTO; import com.luojia_channel.modules.user.dto.UserRegisterDTO;
import com.luojia_channel.modules.user.entity.User; import com.luojia_channel.modules.user.entity.User;
import com.luojia_channel.modules.user.vo.UserInfoVO;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
public interface UserLoginService extends IService<User> { public interface UserLoginService extends IService<User> {
UserDTO login(UserLoginDTO userLoginDTO); UserInfoVO login(UserLoginDTO userLoginDTO);
UserDTO checkLogin(String accessToken, String refreshToken); 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.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.luojia_channel.common.exception.UserException; import com.luojia_channel.common.exception.UserException;
import com.luojia_channel.common.utils.UserContext; 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.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.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.entity.User;
import com.luojia_channel.modules.user.mapper.UserMapper; import com.luojia_channel.modules.user.mapper.UserMapper;
import com.luojia_channel.modules.user.service.UserInfoService; import com.luojia_channel.modules.user.service.UserInfoService;
@ -68,7 +65,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserMapper, User> implement
} }
@Override @Override
public UserInfoDTO getUserInfo(Long userId) { public UserInfoVO getUserInfo(Long userId) {
// 如果userId为null则获取当前登录用户的ID // 如果userId为null则获取当前登录用户的ID
if (userId == null) { if (userId == null) {
userId = UserContext.getUserId(); userId = UserContext.getUserId();
@ -84,7 +81,7 @@ public class UserInfoServiceImpl extends ServiceImpl<UserMapper, User> implement
} }
// 转换为DTO对象 // 转换为DTO对象
UserInfoDTO userInfoDTO = UserInfoDTO.builder() UserInfoVO userInfoVO = UserInfoVO.builder()
.id(user.getId()) .id(user.getId())
.username(user.getUsername()) .username(user.getUsername())
.phone(user.getPhone()) .phone(user.getPhone())
@ -96,6 +93,6 @@ public class UserInfoServiceImpl extends ServiceImpl<UserMapper, User> implement
.status(user.getStatus()) .status(user.getStatus())
.build(); .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.JWTUtil;
import com.luojia_channel.common.utils.RedisUtil; import com.luojia_channel.common.utils.RedisUtil;
import com.luojia_channel.modules.user.utils.ValidateUserUtil; import com.luojia_channel.modules.user.utils.ValidateUserUtil;
import com.luojia_channel.modules.user.vo.UserInfoVO;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@ -69,16 +70,24 @@ public class UserLoginServiceImpl extends ServiceImpl<UserMapper, User> implemen
/** /**
* token * 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 accessToken = jwtUtil.generateAccessToken(userDTO);
String refreshToken = jwtUtil.generateRefreshToken(userDTO); String refreshToken = jwtUtil.generateRefreshToken(userDTO);
userDTO.setAccessToken(accessToken);
userDTO.setRefreshToken(refreshToken);
//存储refreshToken到redis //存储refreshToken到redis
String key = "refresh_token:" + userDTO.getUserId(); String key = REFRESH_TOKEN_PREFIX + userId;
redisUtil.set(key, refreshToken, EXPIRE_TIME, TimeUnit.DAYS); 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 * @return
*/ */
@Override @Override
public UserDTO login(UserLoginDTO userLoginDTO) { public UserInfoVO login(UserLoginDTO userLoginDTO) {
String userFlag = userLoginDTO.getUserFlag(); String userFlag = userLoginDTO.getUserFlag();
String password = userLoginDTO.getPassword(); String password = userLoginDTO.getPassword();
User user = getUserByFlag(userFlag); User user = getUserByFlag(userFlag);
if (!BCrypt.checkpw(password, user.getPassword())) { if (!BCrypt.checkpw(password, user.getPassword())) {
throw new UserException("密码错误"); 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()) .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(); .build();
generateTokens(userDTO);
return userDTO; return userInfoVO;
} }
/** /**
@ -157,7 +180,13 @@ public class UserLoginServiceImpl extends ServiceImpl<UserMapper, User> implemen
.userId(user.getId()) .userId(user.getId())
.username(user.getUsername()) .username(user.getUsername())
.build(); .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; 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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
@ -10,8 +10,8 @@ import lombok.NoArgsConstructor;
@Builder @Builder
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@Schema(description = "用户信息DTO") @Schema(description = "用户信息VO")
public class UserInfoDTO { public class UserInfoVO {
@Schema( @Schema(
description = "用户ID" description = "用户ID"
) )
@ -56,4 +56,14 @@ public class UserInfoDTO {
description = "状态(1正常2冻结)" description = "状态(1正常2冻结)"
) )
private Integer status; private Integer status;
@Schema(
description = "访问令牌"
)
private String accessToken;
@Schema(
description = "刷新令牌"
)
private String refreshToken;
} }

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

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

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

@ -4,10 +4,48 @@ import router from './router'; // 确保引入了 router
import { createPinia } from 'pinia'; import { createPinia } from 'pinia';
import ELementPlus from 'element-plus'; import ELementPlus from 'element-plus';
import 'element-plus/dist/index.css'; import 'element-plus/dist/index.css';
import { checkLoginStatus } from './utils/auth';
import { useUserStore } from './stores/user';
const app = createApp(App); const app = createApp(App);
const pinia = createPinia(); // 创建 Pinia 实例 const pinia = createPinia(); // 创建 Pinia 实例
app.use(pinia); // 注册 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(router); // 注册 vue-router
app.use(ELementPlus); // 注册 element-plus 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 ChangeInformation from '@/views/ChangeInformation.vue';
import FeedBack from '@/views/FeedBack.vue'; import FeedBack from '@/views/FeedBack.vue';
import PostPublish from '@/views/PostPublish.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 = [ const routes = [
{ {
@ -33,18 +36,21 @@ const routes = [
{ {
path: '/user', path: '/user',
name: 'UserPage', name: 'UserPage',
component: UserPage component: UserPage,
meta: { requiresAuth: true }
}, },
{//通知页面 {//通知页面
path: '/notificationlist', path: '/notificationlist',
name: 'NotificationList', name: 'NotificationList',
component: NotificationList component: NotificationList,
meta: { requiresAuth: true }
}, },
{//详细通知页面 {//详细通知页面
path: '/notification/:id', path: '/notification/:id',
name: 'NotificationDetail', name: 'NotificationDetail',
component: () => import('@/views/NotificationDetail.vue'), component: () => import('@/views/NotificationDetail.vue'),
props: true props: true,
meta: { requiresAuth: true }
}, },
{//反馈页面 {//反馈页面
path: '/feedback', path: '/feedback',
@ -55,13 +61,15 @@ const routes = [
//修改个人信息界面 //修改个人信息界面
path:'/changeinformation', path:'/changeinformation',
name:'ChangeInformation', name:'ChangeInformation',
component:ChangeInformation component:ChangeInformation,
meta: { requiresAuth: true }
}, },
{ {
// 发布帖子页面 // 发布帖子页面
path: '/postpublish', path: '/postpublish',
name: 'PostPublish', name: 'PostPublish',
component: PostPublish component: PostPublish,
meta: { requiresAuth: true }
}, },
{ {
// 管理员页面 // 管理员页面
@ -95,7 +103,7 @@ const routes = [
component: () => import('@/views/admin/AdminCategories.vue') component: () => import('@/views/admin/AdminCategories.vue')
} }
], ],
meta: { requiresAdmin: true } meta: { requiresAdmin: true, requiresAuth: true }
} }
]; ];
@ -104,4 +112,86 @@ const router = createRouter({
routes 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; export default router;

@ -9,18 +9,65 @@ export const useUserStore = defineStore('user', {
avatar: '', // 用户头像 avatar: '', // 用户头像
email: '', // 用户邮箱 email: '', // 用户邮箱
phone: '', // 用户手机号 phone: '', // 用户手机号
studentId: '', // 学号
college: '', // 学院 college: '', // 学院
gender: 0, // 性别,0-未知,1-男,2-女 gender: 0, // 性别,0-未知,1-男,2-女
role: 1, // 角色1-普通用户2-管理员3-超级管理员
status: 1, // 状态1-正常2-冻结
}, },
}), }),
actions: { actions: {
login(userData) { login(userData) {
this.isLoggedIn = true; this.isLoggedIn = true;
this.userInfo = { 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() { logout() {
this.isLoggedIn = false; this.isLoggedIn = false;
this.userInfo = { this.userInfo = {
@ -29,11 +76,21 @@ export const useUserStore = defineStore('user', {
avatar: '', avatar: '',
email: '', email: '',
phone: '', phone: '',
studentId: '',
college: '', college: '',
gender: 0, 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 axios from 'axios';
import { clearAuthTokens } from './auth';
import { ElMessage } from 'element-plus';
const request = axios.create({ const request = axios.create({
timeout: 5000 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( request.interceptors.response.use(
response => { response => {
@ -14,6 +48,106 @@ request.interceptors.response.use(
}, },
error => { error => {
console.error('请求错误:', 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); return Promise.reject(error);
} }
); );
@ -21,13 +155,31 @@ request.interceptors.response.use(
//请求拦截器 //请求拦截器
request.interceptors.request.use( request.interceptors.request.use(
config => { config => {
console.log('Request:',config); console.log('Request:', config.url);
if (!config.url.includes('/captcha') && !config.url.includes('/verify-captcha')) { // 只对非认证相关的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 token = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken'); const refreshToken = localStorage.getItem('refreshToken');
if (token) {
// 检查token格式是否正确
if (token && refreshToken && typeof token === 'string' && typeof refreshToken === 'string') {
config.headers['Authorization'] = `Bearer ${token}`; 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; return config;

@ -6,7 +6,12 @@
<div class="avatar-section"> <div class="avatar-section">
<p class="section-title">头像</p> <p class="section-title">头像</p>
<div class="avatar-preview"> <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"> <img v-if="previewAvatar" :src="previewAvatar" alt="新头像预览" class="new-avatar">
</div> </div>
<label class="upload-btn"> <label class="upload-btn">
@ -134,7 +139,7 @@
</template> </template>
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useUserStore } from '@/stores/user'; import { useUserStore } from '@/stores/user';
import request from '@/utils/request'; import request from '@/utils/request';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
@ -142,24 +147,81 @@ import { useRouter } from 'vue-router'
const userStore = useUserStore(); const userStore = useUserStore();
const userInfo = computed(() => userStore.userInfo); 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({ const formData = ref({
username: userInfo.value.username || '', username: '',
phone: userInfo.value.phone || '', phone: '',
email: userInfo.value.email || '', email: '',
studentId: userInfo.value.studentId || '', studentId: '',
gender: userInfo.value.gender !== undefined ? String(userInfo.value.gender) : '0', gender: '0',
college: userInfo.value.college || '', college: '',
moto: userInfo.value.moto || '', moto: '',
password: userInfo.value.password || '' password: ''
}); });
const avatarFile = ref(null); const avatarFile = ref(null);
const previewAvatar = ref(''); const previewAvatar = ref('');
const currentAvatar = ref(userInfo.value.avatar); const currentAvatar = ref('');
const errors = 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) => { const handleAvatarChange = (e) => {
@ -251,15 +313,20 @@ const handleSubmit = async () => {
if (avatarFile.value) { if (avatarFile.value) {
const formDataObj = new FormData(); const formDataObj = new FormData();
formDataObj.append('file', avatarFile.value); formDataObj.append('file', avatarFile.value);
console.log('正在上传头像...');
const uploadRes = await request.post('/user/info/avatar', formDataObj, { const uploadRes = await request.post('/user/info/avatar', formDataObj, {
headers: { 'Content-Type': 'multipart/form-data' } headers: { 'Content-Type': 'multipart/form-data' }
}); });
if (uploadRes.code !== 200) { console.log('头像上传响应:', uploadRes);
if (uploadRes.code !== 200 || !uploadRes.data) {
throw new Error(uploadRes.msg || '头像上传失败'); throw new Error(uploadRes.msg || '头像上传失败');
} }
avatarUrl = uploadRes.data; // URL
avatarUrl = uploadRes.data;
console.log('获取到的头像URL:', avatarUrl);
currentAvatar.value = avatarUrl; currentAvatar.value = avatarUrl;
userInfo.value.avatar = avatarUrl; //
} }
// //
@ -268,16 +335,25 @@ const handleSubmit = async () => {
phone: formData.value.phone, phone: formData.value.phone,
email: formData.value.email, email: formData.value.email,
studentId: formData.value.studentId, studentId: formData.value.studentId,
avatar: userInfo.value.avatar, avatar: avatarUrl, // 使URL
gender: Number(formData.value.gender), gender: Number(formData.value.gender),
college: formData.value.college, college: formData.value.college,
}; };
console.log('提交的用户信息:', updateInfoData);
const updateInfoRes = await request.post('/user/info/update', updateInfoData); const updateInfoRes = await request.post('/user/info/update', updateInfoData);
if (updateInfoRes.code !== 200) { if (updateInfoRes.code !== 200) {
throw new Error(updateInfoRes.msg || '修改用户信息失败'); 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) { if (formData.value.password) {
const passwordRes = await request.post('/user/info/password', null, { const passwordRes = await request.post('/user/info/password', null, {
@ -288,14 +364,19 @@ const handleSubmit = async () => {
} }
} }
ElMessage.success('修改成功!'); ElMessage.success('修改成功!');
router.push('/'); router.push('/');
} catch (error) { } catch (error) {
console.error('修改失败:', error);
ElMessage.error(`修改失败:${error.message}`); ElMessage.error(`修改失败:${error.message}`);
} }
}; };
//
onMounted(() => {
loadUserInfo();
});
</script> </script>
<style scoped> <style scoped>

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

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

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

Loading…
Cancel
Save