Compare commits

..

No commits in common. 'main' and 'main' have entirely different histories.
main ... main

@ -1,4 +1,3 @@
{
"java.compile.nullAnalysis.mode": "automatic",
"java.configuration.updateBuildConfiguration": "interactive"
"java.compile.nullAnalysis.mode": "automatic"
}

@ -55,4 +55,13 @@
<artifactId>spring-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -18,7 +18,7 @@ public class WebMvcConfig implements WebMvcConfigurer {
"/user/register",
"/user/captcha",
"/user/verify-captcha",
"/user/check-login",
"/user/info/getuserinfo",
"/post/list",
"/post/detail",
"/comment/list",

@ -151,7 +151,7 @@ public final class JWTUtil {
if(ttl < NEED_REFRESH_TTL)
redisUtil.set(redisKey, newRefreshToken, REFRESH_EXPIRATION, TimeUnit.MILLISECONDS);
user.setAccessToken(newAccessToken);
user.setRefreshToken(newRefreshToken);
return user;
}

@ -10,7 +10,6 @@ import java.util.Optional;
public final class UserContext {
private static final ThreadLocal<UserDTO> USER_THREAD_LOCAL = new TransmittableThreadLocal<>();
public static void setUser(UserDTO user) {
USER_THREAD_LOCAL.set(user);
}
@ -40,10 +39,6 @@ public final class UserContext {
return Optional.ofNullable(userInfoDTO).map(UserDTO::getRefreshToken).orElse(null);
}
public static UserDTO getUser() {
return USER_THREAD_LOCAL.get();
}
public static void removeUser() {
USER_THREAD_LOCAL.remove();
}

@ -127,4 +127,13 @@
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

@ -70,22 +70,6 @@
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<mainClass>com.luojia_channel.LuojiaChannelApplication</mainClass>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

@ -35,8 +35,7 @@ public class PostSelector {
double newScore = (1 + likes + comments*0.7 + favorites*0.5)
/ Math.pow(1 + hours/24.0, 1.8);
redisUtil.zAdd("post:hot:"+post.getCategoryId(), post.getId(), newScore);
redisUtil.zAdd("post:hot:all", post.getId(), newScore);
redisUtil.zAdd("post:hot:", post.getId(), newScore);
}
public void calculateCommentScore(Comment comment){

@ -14,6 +14,4 @@ public class PostPageQueryDTO extends ScrollPageRequest {
@Schema(title = "排序类型0表示按时间1表示按热度2表示自定义的推荐算法(暂未实现)")
private Integer type = 0;
private Long categoryId;
}

@ -77,8 +77,5 @@ public class PostBasicInfoDTO {
@Schema(
description = "帖子创建时间"
)
private Long categoryId;
private LocalDateTime createTime;
}

@ -82,8 +82,5 @@ public class PostInfoDTO {
@Schema(
description = "帖子创建时间"
)
private Long categoryId;
private LocalDateTime createTime;
}

@ -91,8 +91,7 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
// TODO 消息通知?
}
postSelector.calculatePostScore(post);
redisUtil.zAdd("post:time:"+post.getCategoryId(), post.getId(), System.currentTimeMillis());
redisUtil.zAdd("post:time:"+"all", post.getId(), System.currentTimeMillis());
redisUtil.zAdd("post:time:", post.getId(), System.currentTimeMillis());
redisUtil.zAdd("post:user:" + userId, post.getId(), System.currentTimeMillis());
return post.getId();
}
@ -120,7 +119,6 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
public void deletePost(Long id) {
validatePostUtil.validatePostOwnership(id);
Long userId = UserContext.getUserId();
Post post = postMapper.selectById(id);
int delete = postMapper.deleteById(id);
if(delete <= 0){
throw new PostException("删除帖子失败");
@ -128,10 +126,8 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
// redisUtil.delete("post:detail:" + id.toString());
// redisUtil.delete("post:of:user:" + UserContext.getUserId());
redisUtil.delete("post:detail:" + id);
redisUtil.zRemove("post:time:"+post.getCategoryId(), id);
redisUtil.zRemove("post:hot:"+post.getCategoryId(), id);
redisUtil.zRemove("post:time:"+"all", id);
redisUtil.zRemove("post:hot:"+"all", id);
redisUtil.zRemove("post:time:", id);
redisUtil.zRemove("post:hot:", id);
redisUtil.zRemove("post:user:" + userId, id);
}
@ -188,12 +184,7 @@ public class PostServiceImpl extends ServiceImpl<PostMapper, Post> implements Po
return postBasicInfoDTO;
});
*/
String key;
if(postPageQueryDTO.getCategoryId() == null || postPageQueryDTO.getCategoryId().equals(0L)){
key = (postPageQueryDTO.getType().equals(0) ? "post:time:" : "post:hot:") + "all";
}else {
key = (postPageQueryDTO.getType().equals(0) ? "post:time:" : "post:hot:") + postPageQueryDTO.getCategoryId();
}
String key = postPageQueryDTO.getType().equals(0) ? "post:time:" : "post:hot:";
return redisUtil.scrollPageQuery(key, PostBasicInfoDTO.class, postPageQueryDTO,
(postIds) -> {
List<Long> userIds = new ArrayList<>();

@ -20,7 +20,6 @@ import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import java.awt.image.BufferedImage;
@ -33,7 +32,6 @@ import java.util.concurrent.TimeUnit;
@RequestMapping("/user")
@RequiredArgsConstructor
@Tag(name = "用户管理", description = "用户登陆注册相关接口")
@Slf4j
public class UserLoginController {
private final UserLoginService userLoginService;
private final RedisUtil redisUtil;
@ -64,28 +62,10 @@ public class UserLoginController {
})
public Result<UserDTO> checkLogin(@RequestHeader(value = "Authorization", required = false) String accessToken,
@RequestHeader(value = "X-Refresh-Token", required = false) String refreshToken) {
log.info("检查登录状态 - 接收到的Authorization: {}", accessToken);
log.info("检查登录状态 - 接收到的X-Refresh-Token: {}", refreshToken);
// 检查token是否为空
if (accessToken == null || accessToken.isEmpty()) {
log.error("登录验证失败 - accessToken为空");
return Result.fail("未提供访问令牌");
}
if (refreshToken == null || refreshToken.isEmpty()) {
log.error("登录验证失败 - refreshToken为空");
return Result.fail("未提供刷新令牌");
}
try {
UserDTO userDTO = userLoginService.checkLogin(accessToken, refreshToken);
log.info("登录验证成功 - 用户ID: {}, 用户名: {}", userDTO.getUserId(), userDTO.getUsername());
return Result.success(userDTO);
} catch (Exception e) {
log.error("登录验证失败 - 错误信息: {}", e.getMessage(), e);
return Result.fail(e.getMessage());
if (accessToken != null && accessToken.startsWith("Bearer ")) {
accessToken = accessToken.substring(7);
}
return Result.success(userLoginService.checkLogin(accessToken, refreshToken));
}
@PostMapping("/register")

@ -1,3 +1,4 @@
##本地开发环境
#lj:
# db:
# host: 192.168.59.129
@ -48,6 +49,6 @@ lj:
username: guest
password: guest
minio:
endpoint: http://localhost:9000
accessKey: root
secretKey: 12345678
endpoint: http://localhost:9005
accessKey: leezt
secretKey: lzt264610

@ -49,10 +49,6 @@ spring:
concurrency: 5
max-concurrency: 10
prefetch: 1
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
# minio配置
minio:
endpoint: ${lj.minio.endpoint}

@ -72,9 +72,9 @@
}
},
"node_modules/@babel/compat-data": {
"version": "7.27.5",
"resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.27.5.tgz",
"integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==",
"version": "7.27.2",
"resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.27.2.tgz",
"integrity": "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ==",
"dev": true,
"license": "MIT",
"engines": {
@ -82,22 +82,22 @@
}
},
"node_modules/@babel/core": {
"version": "7.27.4",
"resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.27.4.tgz",
"integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.27.1.tgz",
"integrity": "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.27.3",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.27.3",
"@babel/helpers": "^7.27.4",
"@babel/parser": "^7.27.4",
"@babel/template": "^7.27.2",
"@babel/traverse": "^7.27.4",
"@babel/types": "^7.27.3",
"@babel/generator": "^7.27.1",
"@babel/helper-compilation-targets": "^7.27.1",
"@babel/helper-module-transforms": "^7.27.1",
"@babel/helpers": "^7.27.1",
"@babel/parser": "^7.27.1",
"@babel/template": "^7.27.1",
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.1",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@ -113,9 +113,9 @@
}
},
"node_modules/@babel/eslint-parser": {
"version": "7.27.5",
"resolved": "https://registry.npmmirror.com/@babel/eslint-parser/-/eslint-parser-7.27.5.tgz",
"integrity": "sha512-HLkYQfRICudzcOtjGwkPvGc5nF1b4ljLZh1IRDj50lRZ718NAKVgQpIAUX8bfg6u/yuSKY3L7E0YzIV+OxrB8Q==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/eslint-parser/-/eslint-parser-7.27.1.tgz",
"integrity": "sha512-q8rjOuadH0V6Zo4XLMkJ3RMQ9MSBqwaDByyYB0izsYdaIWGNLmEblbCOf1vyFHICcg16CD7Fsi51vcQnYxmt6Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -132,14 +132,14 @@
}
},
"node_modules/@babel/generator": {
"version": "7.27.5",
"resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.27.5.tgz",
"integrity": "sha512-ZGhA37l0e/g2s1Cnzdix0O3aLYm66eF8aufiVteOgnwxgnRP8GoyMj7VWsgWnQbVKXyge7hqrFh2K2TQM6t1Hw==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.27.1.tgz",
"integrity": "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.5",
"@babel/types": "^7.27.3",
"@babel/parser": "^7.27.1",
"@babel/types": "^7.27.1",
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25",
"jsesc": "^3.0.2"
@ -149,13 +149,13 @@
}
},
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.27.3",
"resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.1.tgz",
"integrity": "sha512-WnuuDILl9oOBbKnb4L+DyODx7iC47XfzmNCpTttFsSp6hTG7XZxu60+4IO+2/hPfcGOoKbFiwoI/+zwARbNQow==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.3"
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@ -264,15 +264,15 @@
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.27.3",
"resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.3.tgz",
"integrity": "sha512-dSOvYwvyLsWBeIRyOeHXp5vPj5l1I011r52FM1+r1jCERv+aFXYk4whgQccYEGYxK2H3ZAIA8nuPkQ0HaUo3qg==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.27.1.tgz",
"integrity": "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.27.1",
"@babel/helper-validator-identifier": "^7.27.1",
"@babel/traverse": "^7.27.3"
"@babel/traverse": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@ -398,14 +398,14 @@
}
},
"node_modules/@babel/helpers": {
"version": "7.27.4",
"resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.4.tgz",
"integrity": "sha512-Y+bO6U+I7ZKaM5G5rDUZiYfUvQPUibYmAFe7EnKdnKBbVXDZxvp+MWOH5gYciY0EPk4EScsuFMQBbEfpdRKSCQ==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.1.tgz",
"integrity": "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/template": "^7.27.2",
"@babel/types": "^7.27.3"
"@babel/template": "^7.27.1",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
@ -506,12 +506,12 @@
}
},
"node_modules/@babel/parser": {
"version": "7.27.5",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.5.tgz",
"integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
"version": "7.27.2",
"resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.2.tgz",
"integrity": "sha512-QYLs8299NA7WM/bZAdp+CviYYkVoYXlDW2rzliy3chxd1PQjej7JORuMJDJXJUb9g0TT+B99EwaVLKmX+sPXWw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.3"
"@babel/types": "^7.27.1"
},
"bin": {
"parser": "bin/babel-parser.js"
@ -816,9 +816,9 @@
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.27.5",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.5.tgz",
"integrity": "sha512-JF6uE2s67f0y2RZcm2kpAUEbD50vH62TyWVebxwHAlbSdM49VqPz8t4a1uIjp4NIOIZ4xzLfjY5emt/RCyC7TQ==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.27.1.tgz",
"integrity": "sha512-QEcFlMl9nGTgh1rn2nIeU5bkfb9BAjaQcWbiP4LvKxUot52ABcTkpcyJ7f2Q2U2RuQ84BNLgts3jRme2dTx6Fw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -904,9 +904,9 @@
}
},
"node_modules/@babel/plugin-transform-destructuring": {
"version": "7.27.3",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz",
"integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.1.tgz",
"integrity": "sha512-ttDCqhfvpE9emVkXbPD8vyxxh4TWYACVybGkDj+oReOGwnp066ITEivDlLwe0b1R0+evJ13IXQuLNB5w1fhC5Q==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1252,15 +1252,15 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.27.3",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz",
"integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==",
"version": "7.27.2",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.2.tgz",
"integrity": "sha512-AIUHD7xJ1mCrj3uPozvtngY3s0xpv7Nu7DoUSnzNY6Xam1Cy4rUznR//pvMHOhQ4AvbCexhbqXCtpxGHOGOO6g==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/plugin-transform-destructuring": "^7.27.3",
"@babel/plugin-transform-destructuring": "^7.27.1",
"@babel/plugin-transform-parameters": "^7.27.1"
},
"engines": {
@ -1388,9 +1388,9 @@
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.27.5",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.5.tgz",
"integrity": "sha512-uhB8yHerfe3MWnuLAhEbeQ4afVoqv8BQsPqrTv7e/jZ9y00kJL6l9a/f4OWaKxotmjzewfEyXE1vgDJenkQ2/Q==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.27.1.tgz",
"integrity": "sha512-B19lbbL7PMrKr52BNPjCqg1IyNUIjTcxKj8uX9zHO+PmWN93s19NDr/f69mIkEp2x9nmDJ08a7lgHaTTzvW7mw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1437,9 +1437,9 @@
}
},
"node_modules/@babel/plugin-transform-runtime": {
"version": "7.27.4",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.4.tgz",
"integrity": "sha512-D68nR5zxU64EUzV8i7T3R5XP0Xhrou/amNnddsRQssx6GrTLdZl1rLxyjtVZBd+v/NVX4AbTPOB5aU8thAZV1A==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.27.1.tgz",
"integrity": "sha512-TqGF3desVsTcp3WrJGj4HfKokfCXCLcHpt4PJF0D8/iT6LPd9RS82Upw3KPeyr6B22Lfd3DO8MVrmp0oRkUDdw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -1705,9 +1705,9 @@
}
},
"node_modules/@babel/runtime": {
"version": "7.27.4",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.4.tgz",
"integrity": "sha512-t3yaEOuGu9NlIZ+hIeGbBjFtZT7j2cb2tg0fuaJKeGotchRjjLfrBA9Kwf8quhpP1EUuxModQg04q/mBwyg8uA==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.27.1.tgz",
"integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==",
"dev": true,
"license": "MIT",
"engines": {
@ -1730,17 +1730,17 @@
}
},
"node_modules/@babel/traverse": {
"version": "7.27.4",
"resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.27.4.tgz",
"integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.27.1.tgz",
"integrity": "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
"@babel/generator": "^7.27.3",
"@babel/parser": "^7.27.4",
"@babel/template": "^7.27.2",
"@babel/types": "^7.27.3",
"@babel/generator": "^7.27.1",
"@babel/parser": "^7.27.1",
"@babel/template": "^7.27.1",
"@babel/types": "^7.27.1",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@ -1749,9 +1749,9 @@
}
},
"node_modules/@babel/types": {
"version": "7.27.3",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.3.tgz",
"integrity": "sha512-Y1GkI4ktrtvmawoSq+4FCVHNryea6uR+qUQy0AGxLSsjCX0nVmkYQMBLHDkXZuo5hGx7eYdnIaslsdBFm7zbUw==",
"version": "7.27.1",
"resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.1.tgz",
"integrity": "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@ -1840,21 +1840,21 @@
}
},
"node_modules/@floating-ui/core": {
"version": "1.7.1",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.1.tgz",
"integrity": "sha512-azI0DrjMMfIug/ExbBaeDVJXcY0a7EPvPjb2xAJPa4HeimBX+Z18HK8QQR3jb6356SnDDdxx+hinMLcJEDdOjw==",
"version": "1.7.0",
"resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.7.0.tgz",
"integrity": "sha512-FRdBLykrPPA6P76GGGqlex/e7fbe0F1ykgxHYNXQsH/iTEtjMj/f9bpY5oQqbjt5VgZvgz/uKXbGuROijh3VLA==",
"license": "MIT",
"dependencies": {
"@floating-ui/utils": "^0.2.9"
}
},
"node_modules/@floating-ui/dom": {
"version": "1.7.1",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.1.tgz",
"integrity": "sha512-cwsmW/zyw5ltYTUeeYJ60CnQuPqmGwuGVhG9w0PRaRKkAyi38BT5CKrpIbb+jtahSwUl04cWzSx9ZOIxeS6RsQ==",
"version": "1.7.0",
"resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.7.0.tgz",
"integrity": "sha512-lGTor4VlXcesUMh1cupTUTDoCxMb0V6bm3CnxHzQcw8Eaf1jQbgQX4i02fYgT0vJ82tb5MZ4CZk1LRGkktJCzg==",
"license": "MIT",
"dependencies": {
"@floating-ui/core": "^1.7.1",
"@floating-ui/core": "^1.7.0",
"@floating-ui/utils": "^0.2.9"
}
},
@ -2186,9 +2186,9 @@
"license": "MIT"
},
"node_modules/@types/express": {
"version": "4.17.22",
"resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.22.tgz",
"integrity": "sha512-eZUmSnhRX9YRSkplpz0N+k6NljUUn5l3EWZIKZvYzhvMphEuNiyyy1viH/ejgt66JWgALwC/gtSUAeQKtSwW/w==",
"version": "4.17.21",
"resolved": "https://registry.npmmirror.com/@types/express/-/express-4.17.21.tgz",
"integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2256,9 +2256,9 @@
"license": "MIT"
},
"node_modules/@types/lodash": {
"version": "4.17.17",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.17.tgz",
"integrity": "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ==",
"version": "4.17.16",
"resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.16.tgz",
"integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g==",
"license": "MIT"
},
"node_modules/@types/lodash-es": {
@ -2285,9 +2285,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
"version": "22.15.29",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-22.15.29.tgz",
"integrity": "sha512-LNdjOkUDlU1RZb8e1kOIUpN1qQUlzGkEtbVNo53vbrwDg5om6oduhm4SiUaPW5ASTXhAiP0jInWG8Qx9fVlOeQ==",
"version": "22.15.17",
"resolved": "https://registry.npmmirror.com/@types/node/-/node-22.15.17.tgz",
"integrity": "sha512-wIX2aSZL5FE+MR0JlvF87BNVrtFWf6AE6rxSE9X7OwnVvoyCQjpzSRJ+M87se/4QCkCiebQAqrJ0y6fwIyi7nw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -2319,9 +2319,9 @@
"license": "MIT"
},
"node_modules/@types/qs": {
"version": "6.14.0",
"resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.14.0.tgz",
"integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==",
"version": "6.9.18",
"resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.18.tgz",
"integrity": "sha512-kK7dgTYDyGqS+e2Q4aK9X3D7q234CIZ1Bv0q/7Z5IwRDoADNU81xXJK/YVyLbLTZCoIwUoDoffFeF+p/eIklAA==",
"dev": true,
"license": "MIT"
},
@ -2876,53 +2876,53 @@
"license": "ISC"
},
"node_modules/@vue/compiler-core": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.16.tgz",
"integrity": "sha512-AOQS2eaQOaaZQoL1u+2rCJIKDruNXVBZSiUD3chnUrsoX5ZTQMaCvXlWNIfxBJuU15r1o7+mpo5223KVtIhAgQ==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
"integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.2",
"@vue/shared": "3.5.16",
"@babel/parser": "^7.25.3",
"@vue/shared": "3.5.13",
"entities": "^4.5.0",
"estree-walker": "^2.0.2",
"source-map-js": "^1.2.1"
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-dom": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.16.tgz",
"integrity": "sha512-SSJIhBr/teipXiXjmWOVWLnxjNGo65Oj/8wTEQz0nqwQeP75jWZ0n4sF24Zxoht1cuJoWopwj0J0exYwCJ0dCQ==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
"integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-core": "3.5.16",
"@vue/shared": "3.5.16"
"@vue/compiler-core": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"node_modules/@vue/compiler-sfc": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.16.tgz",
"integrity": "sha512-rQR6VSFNpiinDy/DVUE0vHoIDUF++6p910cgcZoaAUm3POxgNOOdS/xgoll3rNdKYTYPnnbARDCZOyZ+QSe6Pw==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz",
"integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.27.2",
"@vue/compiler-core": "3.5.16",
"@vue/compiler-dom": "3.5.16",
"@vue/compiler-ssr": "3.5.16",
"@vue/shared": "3.5.16",
"@babel/parser": "^7.25.3",
"@vue/compiler-core": "3.5.13",
"@vue/compiler-dom": "3.5.13",
"@vue/compiler-ssr": "3.5.13",
"@vue/shared": "3.5.13",
"estree-walker": "^2.0.2",
"magic-string": "^0.30.17",
"postcss": "^8.5.3",
"source-map-js": "^1.2.1"
"magic-string": "^0.30.11",
"postcss": "^8.4.48",
"source-map-js": "^1.2.0"
}
},
"node_modules/@vue/compiler-ssr": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.16.tgz",
"integrity": "sha512-d2V7kfxbdsjrDSGlJE7my1ZzCXViEcqN6w14DOsDrUCHEA6vbnVCpRFfrc4ryCP/lCKzX2eS1YtnLE/BuC9f/A==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz",
"integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.16",
"@vue/shared": "3.5.16"
"@vue/compiler-dom": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"node_modules/@vue/component-compiler-utils": {
@ -3029,53 +3029,53 @@
}
},
"node_modules/@vue/reactivity": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.16.tgz",
"integrity": "sha512-FG5Q5ee/kxhIm1p2bykPpPwqiUBV3kFySsHEQha5BJvjXdZTUfmya7wP7zC39dFuZAcf/PD5S4Lni55vGLMhvA==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.13.tgz",
"integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==",
"license": "MIT",
"dependencies": {
"@vue/shared": "3.5.16"
"@vue/shared": "3.5.13"
}
},
"node_modules/@vue/runtime-core": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.16.tgz",
"integrity": "sha512-bw5Ykq6+JFHYxrQa7Tjr+VSzw7Dj4ldR/udyBZbq73fCdJmyy5MPIFR9IX/M5Qs+TtTjuyUTCnmK3lWWwpAcFQ==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.13.tgz",
"integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.16",
"@vue/shared": "3.5.16"
"@vue/reactivity": "3.5.13",
"@vue/shared": "3.5.13"
}
},
"node_modules/@vue/runtime-dom": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.16.tgz",
"integrity": "sha512-T1qqYJsG2xMGhImRUV9y/RseB9d0eCYZQ4CWca9ztCuiPj/XWNNN+lkNBuzVbia5z4/cgxdL28NoQCvC0Xcfww==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz",
"integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==",
"license": "MIT",
"dependencies": {
"@vue/reactivity": "3.5.16",
"@vue/runtime-core": "3.5.16",
"@vue/shared": "3.5.16",
"@vue/reactivity": "3.5.13",
"@vue/runtime-core": "3.5.13",
"@vue/shared": "3.5.13",
"csstype": "^3.1.3"
}
},
"node_modules/@vue/server-renderer": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.16.tgz",
"integrity": "sha512-BrX0qLiv/WugguGsnQUJiYOE0Fe5mZTwi6b7X/ybGB0vfrPH9z0gD/Y6WOR1sGCgX4gc25L1RYS5eYQKDMoNIg==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.13.tgz",
"integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==",
"license": "MIT",
"dependencies": {
"@vue/compiler-ssr": "3.5.16",
"@vue/shared": "3.5.16"
"@vue/compiler-ssr": "3.5.13",
"@vue/shared": "3.5.13"
},
"peerDependencies": {
"vue": "3.5.16"
"vue": "3.5.13"
}
},
"node_modules/@vue/shared": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.16.tgz",
"integrity": "sha512-c/0fWy3Jw6Z8L9FmTyYfkpM5zklnqqa9+a6dz3DvONRKW2NEbh46BP0FHuLFSWi2TnQEtp91Z6zOWNrU6QiyPg==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz",
"integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==",
"license": "MIT"
},
"node_modules/@vue/vue-loader-v15": {
@ -3998,9 +3998,9 @@
}
},
"node_modules/browserslist": {
"version": "4.25.0",
"resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.25.0.tgz",
"integrity": "sha512-PJ8gYKeS5e/whHBh8xrwYK+dAvEj7JXtz6uTucnMRB8OiGTsKccFekoRrjajPBHV8oOY+2tI4uxeceSimKwMFA==",
"version": "4.24.5",
"resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.5.tgz",
"integrity": "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw==",
"dev": true,
"funding": [
{
@ -4018,8 +4018,8 @@
],
"license": "MIT",
"dependencies": {
"caniuse-lite": "^1.0.30001718",
"electron-to-chromium": "^1.5.160",
"caniuse-lite": "^1.0.30001716",
"electron-to-chromium": "^1.5.149",
"node-releases": "^2.0.19",
"update-browserslist-db": "^1.1.3"
},
@ -4166,9 +4166,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001721",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001721.tgz",
"integrity": "sha512-cOuvmUVtKrtEaoKiO0rSc29jcjwMwX5tOHDy4MgVFEWiUXj4uBMJkwI8MDySkgXidpMiHUcviogAvFi4pA2hDQ==",
"version": "1.0.30001718",
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001718.tgz",
"integrity": "sha512-AflseV1ahcSunK53NfEs9gFWgOEmzr0f+kaMFA4xiLZlr9Hzt7HxcSpIFcnNCUkz6R6dWKa54rUz3HUmI3nVcw==",
"dev": true,
"funding": [
{
@ -5035,9 +5035,9 @@
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"version": "4.4.0",
"resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz",
"integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -5426,16 +5426,16 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.164",
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.164.tgz",
"integrity": "sha512-TXBrF2aZenRjY3wbj5Yc0mZn43lMiSHNkzwPkIxx+vWUB35Kf8Gm/uOYmOJFNQ7SUwWAinbfxX73ANIud65wSA==",
"version": "1.5.152",
"resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.152.tgz",
"integrity": "sha512-xBOfg/EBaIlVsHipHl2VdTPJRSvErNUaqW8ejTq5OlOlIYx1wOllCHsAvAIrr55jD1IYEfdR86miUEt8H5IeJg==",
"dev": true,
"license": "ISC"
},
"node_modules/element-plus": {
"version": "2.9.11",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.11.tgz",
"integrity": "sha512-x4L/6YC8de4JtuE3vpaEugJdQIeHQaHtIYKyk67IeF6dTIiVax45aX4nWOygnh+xX+0gTvL6xO+9BZhPA3G82w==",
"version": "2.9.10",
"resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.9.10.tgz",
"integrity": "sha512-W2v9jWnm1kl/zm4bSvCh8aFCVlxvhG3fmqiDZwyd6WQiWGE595J/mpjcCggEr+49QDgIymhXrpPMOPPSARUbng==",
"license": "MIT",
"dependencies": {
"@ctrl/tinycolor": "^3.4.1",
@ -7616,7 +7616,7 @@
},
"node_modules/jparticles": {
"version": "3.5.0",
"resolved": "https://registry.npmmirror.com/jparticles/-/jparticles-3.5.0.tgz",
"resolved": "https://registry.npmjs.org/jparticles/-/jparticles-3.5.0.tgz",
"integrity": "sha512-qUKP56Xqh2G7TqFKHMPDYzfZKkvsbLGJu+xJI4dh0YGZL26zOCUVV31MkkPWmfd6SaST23mhSvvvEArFd8yApQ==",
"license": "MIT"
},
@ -9148,9 +9148,9 @@
}
},
"node_modules/postcss": {
"version": "8.5.4",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.4.tgz",
"integrity": "sha512-QSa9EBe+uwlGTFmHsPKokv3B/oEMQZxfqW0QqNCyhpa6mB1afzulwn8hihglqAb2pOw+BJgNlmXQ8la2VeHB7w==",
"version": "8.5.3",
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.3.tgz",
"integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==",
"funding": [
{
"type": "opencollective",
@ -9167,7 +9167,7 @@
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11",
"nanoid": "^3.3.8",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
@ -10661,9 +10661,9 @@
}
},
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"version": "1.8.2",
"resolved": "https://registry.npmmirror.com/shell-quote/-/shell-quote-1.8.2.tgz",
"integrity": "sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==",
"dev": true,
"license": "MIT",
"engines": {
@ -11179,9 +11179,9 @@
"license": "MIT"
},
"node_modules/tapable": {
"version": "2.2.2",
"resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.2.tgz",
"integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==",
"version": "2.2.1",
"resolved": "https://registry.npmmirror.com/tapable/-/tapable-2.2.1.tgz",
"integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==",
"dev": true,
"license": "MIT",
"engines": {
@ -11189,14 +11189,14 @@
}
},
"node_modules/terser": {
"version": "5.40.0",
"resolved": "https://registry.npmmirror.com/terser/-/terser-5.40.0.tgz",
"integrity": "sha512-cfeKl/jjwSR5ar7d0FGmave9hFGJT8obyo0z+CrQOylLDbk7X81nPU6vq9VORa5jU30SkDnT2FXjLbR8HLP+xA==",
"version": "5.39.0",
"resolved": "https://registry.npmmirror.com/terser/-/terser-5.39.0.tgz",
"integrity": "sha512-LBAhFyLho16harJoWMg/nZsQYgTrg5jXOn2nCYjRUcZZEdE3qa2zb8QEDRUGVZBW4rlazf2fxkg8tztybTaqWw==",
"dev": true,
"license": "BSD-2-Clause",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.14.0",
"acorn": "^8.8.2",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
@ -11660,16 +11660,16 @@
}
},
"node_modules/vue": {
"version": "3.5.16",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.16.tgz",
"integrity": "sha512-rjOV2ecxMd5SiAmof2xzh2WxntRcigkX/He4YFJ6WdRvVUrbt6DxC1Iujh10XLl8xCDRDtGKMeO3D+pRQ1PP9w==",
"version": "3.5.13",
"resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.13.tgz",
"integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==",
"license": "MIT",
"dependencies": {
"@vue/compiler-dom": "3.5.16",
"@vue/compiler-sfc": "3.5.16",
"@vue/runtime-dom": "3.5.16",
"@vue/server-renderer": "3.5.16",
"@vue/shared": "3.5.16"
"@vue/compiler-dom": "3.5.13",
"@vue/compiler-sfc": "3.5.13",
"@vue/runtime-dom": "3.5.13",
"@vue/server-renderer": "3.5.13",
"@vue/shared": "3.5.13"
},
"peerDependencies": {
"typescript": "*"
@ -11870,9 +11870,9 @@
"license": "MIT"
},
"node_modules/watchpack": {
"version": "2.4.4",
"resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.4.tgz",
"integrity": "sha512-c5EGNOiyxxV5qmTtAB7rbiXxi1ooX1pQKMLX/MIabJjRA0SJBQOjKF+KSVfHkr9U1cADPon0mRiVe/riyaiDUA==",
"version": "2.4.2",
"resolved": "https://registry.npmmirror.com/watchpack/-/watchpack-2.4.2.tgz",
"integrity": "sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -11911,9 +11911,9 @@
"license": "BSD-2-Clause"
},
"node_modules/webpack": {
"version": "5.99.9",
"resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.99.9.tgz",
"integrity": "sha512-brOPwM3JnmOa+7kd3NsmOUOwbDAj8FT9xDsG3IW0MgbN9yZV7Oi/s/+MNQ/EcSMqw7qfoRyXPoeEWT8zLVdVGg==",
"version": "5.99.8",
"resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.99.8.tgz",
"integrity": "sha512-lQ3CPiSTpfOnrEGeXDwoq5hIGzSjmwD72GdfVzF7CQAI7t47rJG9eDWvcEkEn3CUQymAElVvDg3YNTlCYj+qUQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -12246,9 +12246,9 @@
}
},
"node_modules/webpack-sources": {
"version": "3.3.2",
"resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.3.2.tgz",
"integrity": "sha512-ykKKus8lqlgXX/1WjudpIEjqsafjOTcOJqxnAbMLAu/KCsDCJ6GBtvscewvTkrn24HsnvFwrSCbenFrhtcCsAA==",
"version": "3.2.3",
"resolved": "https://registry.npmmirror.com/webpack-sources/-/webpack-sources-3.2.3.tgz",
"integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==",
"dev": true,
"license": "MIT",
"engines": {

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -4,8 +4,8 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>logo.ico">
<title>珞珈岛</title>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title>Responsive Login And Registration</title>
<link href='<%= BASE_URL %>css/boxicons.min.css' rel='stylesheet'>
<link rel="stylesheet" href="<%= BASE_URL %>css/style.css">
</head>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 KiB

@ -23,11 +23,11 @@ function getRandomInt(min, max) {
onMounted(() => {
new Snow('#snow',
{
num: getRandomInt(1,2),
num: getRandomInt(1,4),
color: '#fff',
maxR: 3,
minR: 12,
maxSpeed: 0.3,
maxSpeed: 0.4,
minSpeed: 0.1,
swing: true,
swingProbability: 0.1,
@ -46,21 +46,6 @@ onMounted(() => {
text-align: center;
color: #2c3e50;
margin-top: 60px;
position: relative;
z-index: 1;
}
/* 背景图层 */
#app::before {
content: "";
position: fixed;
top: 0; left: 0; right: 0; bottom: 0;
width: 100vw;
height: 100vh;
z-index: -1;
background: url('./assets/background.jpg') no-repeat center center / cover;
opacity: 0.3; /* 不透明度,可调整 */
pointer-events: none;
}
.snow {
position: fixed;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 992 KiB

File diff suppressed because one or more lines are too long

@ -97,18 +97,11 @@ const defaultAvatar = require("@/assets/default-avatar/boy_1.png");
//
const isLoggedIn = computed(() => userStore.isLoggedIn);
const userInfo = computed(() => {
// userStore
const info = userStore.userInfo;
// userStorelocalStorage
if (!info.avatar) {
info.avatar = localStorage.getItem('avatar') || '';
}
return info;
return userStore.userInfo;
});
const showAdmin = computed(() => {
const isAdmin = userStore.isLoggedIn && (userInfo.value.role >= 2);
console.log('检查管理员权限:', userStore.isLoggedIn, userInfo.value.role, isAdmin);
return isAdmin;
});
@ -160,47 +153,16 @@ const hideDropdown = () => {
}, 400);
};
//
onMounted(async () => {
//
onMounted(() => {
if (userStore.isLoggedIn) {
// token
const accessToken = localStorage.getItem('accessToken');
const refreshToken = localStorage.getItem('refreshToken');
if (!accessToken || !refreshToken) {
console.warn('没有找到token跳过获取用户信息');
return;
}
try {
//
const response = await request.get('/user/info/getuserinfo');
if (response && response.code === 200) {
userStore.updateUserInfo({
userid: response.data.id,
username: response.data.username,
avatar: response.data.avatar,
email: response.data.email,
phone: response.data.phone,
college: response.data.college,
gender: response.data.gender,
role: response.data.role,
status: response.data.status
});
}
} catch (error) {
console.error('获取用户信息失败:', error);
// 401tokentoken
if (error.response && (error.response.status === 401 ||
(error.response.status === 500 && error.response.data?.message?.includes('token')))) {
console.warn('Token可能无效不再尝试获取用户信息');
}
}
console.log('Header组件挂载: 检测到用户已登录:', userStore.userInfo.username, '角色:', userStore.userInfo.role);
}
});
//
watch(() => userStore.userInfo, () => {
watch(() => userStore.userInfo, (newInfo) => {
console.log('用户信息更新:', newInfo.username, '角色:', newInfo.role);
}, { deep: true });
</script>
@ -211,13 +173,12 @@ watch(() => userStore.userInfo, () => {
align-items: center;
padding: 0 20px; /* 增加左右内边距,避免内容过于靠边 */
border-bottom: 2px solid #e0e0e0;
background: rgba(255, 255, 255, 0.5);
background: white;
position: fixed;
top: 0;
width: 100%;
z-index: 1000;
box-sizing: border-box; /* 确保内边距不会影响宽度 */
}
/* Logo 区域样式 */

@ -205,47 +205,14 @@ async function login() {
userData.userName = userData.username;
userData.userid = userData.id;
// storelocalStorage
userStore.login(userData);
//
try {
const userInfoResponse = await request.get('/user/info/getuserinfo', {
params: { userId: userData.id },
headers: {
'Authorization': `Bearer ${userData.accessToken}`,
'X-Refresh-Token': userData.refreshToken
}
});
if (userInfoResponse && userInfoResponse.code === 200) {
//
userStore.updateUserInfo({
userid: userInfoResponse.data.id || userData.id,
username: userInfoResponse.data.username,
avatar: userInfoResponse.data.avatar,
email: userInfoResponse.data.email,
phone: userInfoResponse.data.phone,
college: userInfoResponse.data.college,
gender: userInfoResponse.data.gender,
role: userInfoResponse.data.role,
status: userInfoResponse.data.status
});
}
} catch (error) {
//
}
ElMessage({
message: '登录成功',
type: 'success',
duration: 500
});
emit('LoginSuccess'); //
// 使
window.location.reload();
} else {
//
ElMessage({

@ -2,20 +2,19 @@
<div class="notice-board">
<h2 class="notice-title">公告栏</h2>
<div class="notice-list">
<!-- 左箭头按钮 -->
<button class="arrow-button left-arrow" @click="prevImage"></button>
<div class="notice-item">
<img
v-if="images.length"
:src="images[currentIndex].src"
:alt="images[currentIndex].alt"
class="notice-image"
style="cursor:pointer"
@click="goToPostDetail(images[currentIndex].postId)"
/>
<div v-else class="notice-empty">暂无活动公告</div>
</div>
<!-- 右箭头按钮 -->
<button class="arrow-button right-arrow" @click="nextImage"></button>
</div>
<!-- 圆形按钮 -->
<div class="indicator-container">
<button
v-for="(image, index) in images"
@ -30,54 +29,26 @@
<script setup lang="js" name="NoticeBoard">
import { ref, onMounted, onUnmounted } from 'vue';
import request from '@/utils/request';
import { useRouter } from 'vue-router';
const images = ref([]);
const currentIndex = ref(0);
const router = useRouter();
// 5
const fetchImages = async () => {
try {
// size
const lastVal = Date.now();
const offset = 0;
const size = 5;
let url = `/post/list?lastVal=${lastVal}&offset=${offset}&size=${size}`;
url += `&categoryId=1`;
const res = await request.get(url);
if (res.code === 200 && res.data.records) {
images.value = res.data.records
.filter(post => post.image) //
.map(post => ({
src: post.image,
alt: post.title || '校园活动',
postId: post.id
}));
}
} catch (e) {
images.value = [];
}
};
const images = ref([
{ src: require('@/assets/whu1.jpg'), alt: '公告图片1' },
{ src: require('@/assets/whu2.jpg'), alt: '公告图片2' },
{ src: require('@/assets/whu3.jpg'), alt: '公告图片3' },
{ src: require('@/assets/whu4.jpg'), alt: '公告图片4' },
{ src: require('@/assets/whu5.jpg'), alt: '公告图片5' },
]);
//
const goToPostDetail = (postId) => {
if (postId) {
router.push({ name: 'PostDetail', params: { id: postId } });
}
};
const currentIndex = ref(0);
//
const nextImage = () => {
if (images.value.length)
currentIndex.value = (currentIndex.value + 1) % images.value.length;
};
//
const prevImage = () => {
if (images.value.length)
currentIndex.value = (currentIndex.value - 1 + images.value.length) % images.value.length;
currentIndex.value =
(currentIndex.value - 1 + images.value.length) % images.value.length;
};
//
@ -88,8 +59,7 @@ const goToImage = (index) => {
//
let interval = null;
onMounted(async () => {
await fetchImages();
onMounted(() => {
interval = setInterval(nextImage, 5000);
});
@ -101,17 +71,16 @@ onUnmounted(() => {
<style scoped>
.notice-board {
position: relative;
background-color: rgb(90, 167, 111,0.8);
background-color: #5aa76f;
display: flex;
flex-direction: column;
align-items: center;
justify-content: flex-start;
width: 100%;
height: 100%;
min-width: 220px;
max-width: 700px;
overflow: hidden;
box-sizing: border-box;
justify-content: flex-start; /* 顶部对齐 */
width: 500px; /* 固定宽度 */
height: 350px; /* 固定高度 */
overflow: hidden; /* 防止图片溢出 */
padding: 10px; /* 内边距 */
box-sizing: border-box; /* 包括内边距在宽高内 */
}
.notice-title {
@ -128,34 +97,20 @@ onUnmounted(() => {
justify-content: center;
position: relative;
width: 100%;
height: calc(100% - 60px); /* 保证展示区高度 */
min-height: 120px;
height: calc(100% - 60px); /* 除去标题和指示器的高度 */
}
.notice-item {
text-align: center;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.notice-image {
width: 100%;
height: 100%;
object-fit: cover;
object-fit: cover; /* 让图片适应公告栏 */
border-radius: 8px;
display: block;
}
.notice-empty {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #999;
font-size: 36px;
}
.arrow-button {
@ -189,7 +144,7 @@ onUnmounted(() => {
justify-content: center;
align-items: center;
margin-top: 10px;
gap: 8px;
gap: 8px; /* 按钮之间的间距 */
}
.indicator-button {

@ -1,7 +1,7 @@
<template>
<div class="post-page-container">
<!-- 顶部横向分类按钮 -->
<div class="category-buttons-horizontal" ref="categoryBarRef">
<!-- 左侧分类按钮 -->
<div class="category-buttons">
<button
v-for="(category, index) in categories"
:key="index"
@ -13,114 +13,72 @@
</button>
</div>
<!-- 帖子列表 -->
<!-- 右侧帖子列表 -->
<div class="post-list">
<div
v-for="(post, index) in postListStore.posts"
v-for="(post, index) in filteredPosts"
:key="index"
class="post-item"
@click="goToPostDetail(post.id)"
>
<!-- 头像+用户名 -->
<div class="post-header">
<img
:src="(post.userName === '匿名用户' || post?.status === 1)
? require('@/assets/default-avatar/boy_4.png')
: post.avatar || require('@/assets/default-avatar/boy_1.png')"
alt="头像"
class="post-avatar"
/>
<span class="post-username">{{ post.userName }}</span>
<img :src="post.avatar" :alt="头像" class="post-avatar" />
<h3 class="post-title">{{ post.title }}</h3>
</div>
<!-- 标题 -->
<div class="post-title">{{ post.title }}</div>
<!-- 图片 -->
<div v-if="post.image" class="post-image-wrapper">
<img :src="post.image" alt="帖子图片" class="post-image" />
</div>
<!-- 统计信息 -->
<p class="post-summary">{{ post.summary }}</p>
<div class="post-stats">
<span>👁 {{ post.viewCount }}</span>
<span>🗨 {{ post.comments }}</span>
<span> {{ post.likes }}</span>
</div>
<!-- 发布时间 -->
<div class="post-time">
{{ post.createTime ? post.createTime.slice(0, 10) : '' }}
</div>
</div>
</div>
<!-- 回到顶部按钮 -->
<button
v-if="showBackToTop"
class="back-to-top-btn"
@click="scrollToTop"
>
🔝
</button>
</div>
</template>
<script setup lang="js" name="PostPage">
import { ref, onMounted, onUnmounted, watch } from 'vue';
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
import { useRouter } from 'vue-router';
import { usePostListStore } from '@/stores/postlist.js';
const categoryMap = [
{ name: '全部', id: null },
{ name: '校园活动', id: 1 },
{ name: '学习', id: 2 },
{ name: '娱乐', id: 3 },
{ name: '二手交易', id: 4 }
];
const categories = ref(categoryMap.map(item => item.name));
const categories = ref(['全部', '学习', '娱乐', '二手交易']);
const selectedCategory = ref('全部');
const postListStore = usePostListStore();
const router = useRouter();
const categoryBarRef = ref(null);
const showBackToTop = ref(false);
//
const filteredPosts = computed(() => {
if (selectedCategory.value === '全部') return postListStore.posts;
return postListStore.posts.filter(post => post.category === selectedCategory.value);
});
const selectCategory = async (categoryName) => {
selectedCategory.value = categoryName;
//
const selectCategory = async (category) => {
selectedCategory.value = category;
postListStore.resetList();
const categoryObj = categoryMap.find(item => item.name === categoryName);
await postListStore.getList(categoryObj ? categoryObj.id : null);
await postListStore.getList({});
};
//
const goToPostDetail = (postId) => {
router.push({ name: 'PostDetail', params: { id: postId } });
};
//
const handleScroll = async () => {
const scrollContainer = document.documentElement;
//
const bar = categoryBarRef.value;
if (bar) {
const rect = bar.getBoundingClientRect();
showBackToTop.value = rect.bottom < 0;
} else {
// 200px
showBackToTop.value = scrollContainer.scrollTop > 200;
}
//
if (
scrollContainer.scrollTop + window.innerHeight >= scrollContainer.scrollHeight - 10 &&
!postListStore.loading &&
!postListStore.finished
) {
const categoryObj = categoryMap.find(item => item.name === selectedCategory.value);
await postListStore.getList(categoryObj ? categoryObj.id : null);
await postListStore.getList({});
}
};
const scrollToTop = () => {
window.scrollTo({ top: 0, behavior: 'smooth' });
};
onMounted(async () => {
await postListStore.getList();
//
await postListStore.getList({});
window.addEventListener('scroll', handleScroll);
});
@ -128,175 +86,115 @@ onUnmounted(() => {
window.removeEventListener('scroll', handleScroll);
});
//
watch(selectedCategory, () => {
window.scrollTo(0, 0);
});
</script>
<style scoped>
/* 页面整体布局 */
.post-page-container {
display: flex;
background-color: rgba(249, 227, 238, 0.8);
flex-direction: column;
align-items: stretch;
padding: 5px 0;
flex-direction: row;
align-items: flex-start;
padding: 20px;
box-sizing: border-box;
gap: 1px;
width: 100%;
gap: 20px;
}
/* 顶部横向分类按钮样式 */
.category-buttons-horizontal {
/* 左侧分类按钮样式 */
.category-buttons {
display: flex;
flex-direction: row;
flex-direction: column;
gap: 0;
width: 100%;
margin-bottom: 10px;
border-bottom: 1px solid #eee;
background: rgba(218, 213, 213, 0.6);
padding-bottom: 2px;
width: 200px;
}
.category-button {
padding: 10px 24px;
padding: 10px 20px;
font-size: 14px;
border: none;
background-color: transparent;
border: 1px solid #ccc;
border-bottom: none;
background-color: #f9f9f9;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
color: #333;
border-bottom: 2px solid transparent;
outline: none;
}
.category-button:last-child {
border-bottom: 1px solid #ccc;
}
.category-button.active {
background-color: #f6fff8;
color: #5aa76f;
border-bottom: 2px solid #5aa76f;
font-weight: bold;
background-color: #5aa76f;
color: white;
border-color: #5aa76f;
}
.category-button:hover {
background-color: #f0f0f0;
background-color: #e0e0e0;
}
/* 帖子列表区域占满父容器左右margin为10px */
/* 右侧帖子列表样式 */
.post-list {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
gap: 15px;
padding: 10px;
grid-template-columns: repeat(2, 1fr);
gap: 20px;
flex: 1;
width: 100%;
box-sizing: border-box;
}
/* 帖子卡片自适应宽度和高度 */
.post-item {
position: relative;
position: relative; /* 设置为相对定位 */
width: 300px;
height: 150px;
padding: 15px;
border: 1px solid #ccc;
border-radius: 8px;
background-color: #ffffff;
cursor: pointer;
transition: box-shadow 0.3s;
display: flex;
flex-direction: column;
justify-content: flex-start;
box-sizing: border-box;
width: 100%;
height: 280px;
max-width: 475px;
overflow: hidden;
}
/* 头像+用户名 */
.post-item:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
/* 帖子头部样式 */
.post-header {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 6px;
margin-bottom: 10px;
}
.post-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
object-fit: cover;
width: 40px;
height: 40px;
border-radius: 50%; /* 设置为圆形 */
object-fit: cover; /* 确保图片按比例填充 */
}
.post-username {
font-size: 15px;
color: #5aa76f;
font-weight: bold;
}
/* 标题 */
.post-title {
font-size: 18px;
text-align: left;
font-weight: bold;
margin: 0 0 8px 0;
color: #333;
word-break: break-all;
padding-left: 30px;
margin: 0;
}
/* 图片展示 */
.post-image-wrapper {
width: 100%;
height: 160px;
display: flex;
align-items: center;
justify-content: center;
/* 帖子摘要样式 */
.post-summary {
font-size: 14px;
color: #666;
margin-bottom: 10px;
overflow: hidden;
}
.post-image {
width: 100%;
height: 100%;
object-fit: contain;
display: block;
}
/* 帖子统计信息样式 */
.post-stats {
display: flex;
gap: 15px;
font-size: 16px;
gap: 15px; /* 图标之间的间距 */
font-size: 12px;
color: #999;
position: absolute;
bottom: 5px;
left: 15px;
}
/* 发布时间(右下角) */
.post-time {
position: absolute;
bottom: 5px;
right: 15px;
font-size: 13px;
color: #bbb;
}
/* 回到顶部按钮样式 */
.back-to-top-btn {
position: fixed;
right: 32px;
bottom: 48px;
z-index: 999;
background: #d5e9da;
color: #fff;
border: none;
border-radius: 5px;
font-size: 36px;
box-shadow: 0 2px 8px rgba(90,167,111,0.15);
cursor: pointer;
opacity: 0.85;
transition: opacity 0.2s;
}
.back-to-top-btn:hover {
opacity: 1;
background: #388e3c;
position: absolute; /* 绝对定位 */
bottom: 10px; /* 距离卡片底部 10px */
left: 15px; /* 距离卡片左侧 15px */
}
</style>

@ -2,7 +2,7 @@
<div class="welcome-container">
<!-- 上部分欢迎文字 -->
<div class="welcome-header">
欢迎来到珞珈岛{{ userStore.userInfo?.username ? userStore.userInfo.username : '珈人' }}
欢迎来到珞珈岛珈人
</div>
<!-- 中间部分月份日期星期 -->
@ -33,7 +33,6 @@
<script setup lang="js" name="WelcomeCalendar">
import { ref } from 'vue';
import { useUserStore } from '@/stores/user';
const today = new Date();
@ -41,31 +40,10 @@ const today = new Date();
const currentMonthText = ref(['一', '月']); //
const currentDay = ref(today.getDate()); //
const currentWeekday = ref(['星', '期', '日']); //
const userStore = useUserStore();
//
const isCheckedIn = ref(false);
//
const getUserKey = () => {
return userStore.userInfo?.userid || userStore.userInfo?.username || 'guest';
};
//
const checkToday = () => {
const todayStr = new Date().toISOString().slice(0, 10);
const userKey = getUserKey();
return localStorage.getItem(`checkin-${userKey}-${todayStr}`) === '1';
};
//
const checkIn = () => {
const todayStr = new Date().toISOString().slice(0, 10);
const userKey = getUserKey();
isCheckedIn.value = true;
localStorage.setItem(`checkin-${userKey}-${todayStr}`, '1');
};
//
const initializeDate = () => {
const months = [
@ -77,7 +55,11 @@ const initializeDate = () => {
currentMonthText.value = months[today.getMonth()].split('');
currentDay.value = today.getDate();
currentWeekday.value = weekdays[today.getDay()].split('');
isCheckedIn.value = checkToday();
};
//
const checkIn = () => {
isCheckedIn.value = true;
};
//
@ -90,12 +72,8 @@ initializeDate();
flex-direction: column;
align-items: center;
justify-content: space-between;
width: 100%;
height: 100%;
min-width: 180px;
min-height: 220px;
max-width: 300px;
max-height: 300px;
width: 300px;
height: 350px;
margin: 0 auto;
border: 1px solid #ccc;
border-radius: 8px;
@ -103,8 +81,6 @@ initializeDate();
font-family: Arial, sans-serif;
padding: 20px;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.7); /* 半透明白色背景 */
backdrop-filter: blur(4px); /* 可选,增加毛玻璃效果 */
}
.welcome-header {
@ -112,7 +88,7 @@ initializeDate();
font-weight: bold;
color: #5aa76f;
text-align: center;
margin-bottom: 1px;
margin-bottom: 20px;
}
.welcome-body {
@ -120,7 +96,7 @@ initializeDate();
justify-content: space-between;
align-items: center;
width: 100%;
margin-bottom: 1px;
margin-bottom: 20px;
}
.month-column,
@ -128,19 +104,17 @@ initializeDate();
display: flex;
flex-direction: column;
align-items: center;
font-size: 20px;
font-size: 16px;
font-weight: bold;
color: #333;
gap: 2px;
gap: 2px; /* 紧凑排列 */
}
.day-row {
display: flex;
justify-content: center;
align-items: center;
font-size: 10vw; /* 用vw自适应字体大小 */
min-font-size: 40px;
max-font-size: 120px;
font-size: 180px; /* 日期号字体放大 */
font-weight: bold;
color: #5aa76f;
flex: 1; /* 占据中间空间 */

@ -30,6 +30,7 @@ if (userId && username && accessToken && refreshToken) {
accessToken,
refreshToken
});
console.log('从本地存储恢复用户登录状态:', username);
}
app.use(router); // 注册 vue-router
@ -38,24 +39,13 @@ app.use(ELementPlus); // 注册 element-plus
// 挂载应用
app.mount('#app');
// 在应用启动后立即验证登录状态
async function validateLoginStatus() {
try {
const isLoggedIn = await checkLoginStatus();
// 在应用启动后异步验证登录状态,不阻塞应用启动
setTimeout(() => {
checkLoginStatus()
.then(isLoggedIn => {
console.log('登录状态验证完成,用户登录状态:', isLoggedIn ? '已登录' : '未登录');
// 如果用户未登录但本地存储中有token清除无效的token
if (!isLoggedIn && (localStorage.getItem('accessToken') || localStorage.getItem('refreshToken'))) {
console.warn('本地存储中的token无效清除token');
localStorage.removeItem('accessToken');
localStorage.removeItem('refreshToken');
// 不清除用户基本信息,允许显示用户名等非敏感信息
}
} catch (error) {
console.error('登录状态验证失败:', error);
// 不影响应用使用,只在控制台显示错误
}
}
// 立即执行,但不阻塞应用启动
validateLoginStatus();
})
.catch(error => {
console.error('登录状态验证失败,但不影响应用使用:', error);
});
}, 500);

@ -142,25 +142,16 @@ router.beforeEach(async (to, from, next) => {
});
console.log('从localStorage恢复用户登录状态:', username);
// 立即验证token有效性
try {
console.log('验证恢复的登录状态...');
const isLoggedIn = await checkLoginStatus();
if (!isLoggedIn) {
console.warn('恢复的登录状态无效');
ElMessage.warning('登录已过期,请重新登录');
next({ path: '/user/login', query: { redirect: to.fullPath } });
return;
}
console.log('恢复的登录状态有效,继续导航');
// 异步验证token有效性但不阻塞导航
setTimeout(() => {
checkLoginStatus().catch(err => {
console.error('Token验证失败但不影响当前导航:', err);
});
}, 100);
// 继续导航
next();
return;
} catch (error) {
console.error('登录状态验证失败:', error);
ElMessage.warning('登录状态验证失败,请重新登录');
next({ path: '/user/login', query: { redirect: to.fullPath } });
return;
}
}
// 如果localStorage中没有用户信息尝试通过token验证登录状态
@ -169,29 +160,15 @@ router.beforeEach(async (to, from, next) => {
const isLoggedIn = await checkLoginStatus();
if (!isLoggedIn) {
ElMessage.warning('请先登录');
next({ path: '/user/login', query: { redirect: to.fullPath } });
next({ path: '/' });
return;
}
} catch (error) {
console.error('登录状态验证失败:', error);
ElMessage.warning('登录状态验证失败,请重新登录');
next({ path: '/user/login', query: { redirect: to.fullPath } });
next({ path: '/' });
return;
}
} else {
// 用户已登录但仍然验证一下token有效性不阻塞导航
setTimeout(async () => {
try {
const isLoggedIn = await checkLoginStatus();
if (!isLoggedIn) {
console.warn('登录状态已失效');
// 不中断用户操作,只显示提示
ElMessage.warning('登录已过期,请重新登录');
}
} catch (error) {
console.error('登录状态验证失败:', error);
}
}, 100);
}
}
@ -200,7 +177,7 @@ router.beforeEach(async (to, from, next) => {
// 检查用户是否登录且是否有管理员权限
if (!userStore.isLoggedIn) {
ElMessage.error('请先登录');
next({ path: '/user/login', query: { redirect: to.fullPath } });
next({ path: '/' });
return;
}

@ -131,7 +131,7 @@ export const usePostDetailStore = defineStore("postDetail", {
userAvatar,
createTime
} = postRes.data;
console.log("获取帖子详情返回:", postRes.data);
// 主要帖子信息
this.post = {
postId: id,

@ -6,27 +6,41 @@ export const usePostListStore = defineStore('postList', {
posts: [], // 帖子列表
total: 0, // 帖子总数
page: 1, // 当前页码
pageSize: 6, // 每页帖子数
pageSize: 10, // 每页帖子数
lastVal: Date.now(), // 用于滚动分页的时间戳
offset: 0, // 偏移量
loading: false, // 加载状态
finished: false, // 是否加载完全部
currentCategory: null, // 当前分类id
}),
actions: {
async getList(categoryId = null) {
setPosts(posts) {
this.posts = posts;
},
setTotal(total) {
this.total = total;
},
setPage(page) {
this.page = page;
},
setPageSize(pageSize) {
this.pageSize = pageSize;
},
addPost(post) {
this.posts.push(post);
this.total += 1; // 更新总数
},
removePost(postId) {
this.posts = this.posts.filter(post => post.id !== postId);
this.total -= 1; // 更新总数
},
async getList() {
if (this.loading || this.finished) return;
this.loading = true;
// 记录当前分类
this.currentCategory = categoryId;
try {
// 保证 lastVal 不为 null
const lastVal = (typeof this.lastVal === 'number' && this.lastVal > 0) ? this.lastVal : Date.now();
// 拼接参数到URL
let url = `/post/list?lastVal=${lastVal}&offset=${this.offset}&size=${this.pageSize}`;
if(categoryId!== null) {
url += `&categoryId=${categoryId}`;
}
const url = `/post/list?lastVal=${lastVal}&offset=${this.offset}&size=${this.pageSize}`;
const res = await request.get(url);
if (res.code === 200) {
const { records, lastVal: newLastVal, offset: newOffset, size: newSize } = res.data;
@ -37,10 +51,11 @@ export const usePostListStore = defineStore('postList', {
image: post.image,
avatar: post.userAvatar || require('@/assets/default-avatar/boy_1.png'),
title: post.title,
summary: post.content ? post.content.slice(0, 40) + (post.content.length > 40 ? '...' : '') : '',
viewCount: post.viewCount || 0,
likes: post.likeCount || 0,
comments: post.commentCount || 0,
categoryId: post.categoryId,
category: post.category || '全部',
createTime: post.createTime,
userName: post.userName,
}));
@ -50,7 +65,6 @@ export const usePostListStore = defineStore('postList', {
this.offset = newOffset;
this.pageSize = newSize;
}
console.log('获取帖子列表成功', this.posts);
if (!records || records.length < this.pageSize) {
this.finished = true; // 没有更多数据
}

@ -20,12 +20,12 @@ export const useUserStore = defineStore('user', {
this.isLoggedIn = true;
this.userInfo = {
userid: userData.userid || 0,
username: userData.userName || userData.username || '',
username: userData.userName || '',
avatar: userData.avatar || '',
email: userData.email || '',
phone: userData.phone || '',
college: userData.college || '',
gender: userData.gender !== undefined ? userData.gender : 0,
gender: userData.gender || 0,
role: userData.role || 1,
status: userData.status || 1
};
@ -38,22 +38,19 @@ export const useUserStore = defineStore('user', {
localStorage.setItem('refreshToken', userData.refreshToken);
}
// 保存完整用户信息到localStorage用于页面刷新时恢复
// 保存基本用户信息到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);
localStorage.setItem('email', this.userInfo.email || '');
localStorage.setItem('phone', this.userInfo.phone || '');
localStorage.setItem('college', this.userInfo.college || '');
localStorage.setItem('gender', this.userInfo.gender !== undefined ? String(this.userInfo.gender) : '0');
console.log('用户登录成功,状态已更新:', this.userInfo);
},
// 更新用户信息
updateUserInfo(userData) {
this.userInfo = {
...this.userInfo,
userid: userData.userid || this.userInfo.userid,
username: userData.username || this.userInfo.username,
avatar: userData.avatar || this.userInfo.avatar,
email: userData.email || this.userInfo.email,
@ -65,14 +62,10 @@ export const useUserStore = defineStore('user', {
};
// 更新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);
localStorage.setItem('email', this.userInfo.email || '');
localStorage.setItem('phone', this.userInfo.phone || '');
localStorage.setItem('college', this.userInfo.college || '');
localStorage.setItem('gender', this.userInfo.gender !== undefined ? String(this.userInfo.gender) : '0');
console.log('用户信息已更新:', this.userInfo);
},
logout() {
@ -96,10 +89,8 @@ export const useUserStore = defineStore('user', {
localStorage.removeItem('username');
localStorage.removeItem('avatar');
localStorage.removeItem('role');
localStorage.removeItem('email');
localStorage.removeItem('phone');
localStorage.removeItem('college');
localStorage.removeItem('gender');
}
}
console.log('用户已登出');
},
},
});

@ -13,105 +13,49 @@ export async function checkLoginStatus() {
// 如果没有token直接返回未登录
if (!accessToken || !refreshToken) {
console.log('没有找到token用户未登录');
console.log('本地没有找到token无需验证登录状态');
return false;
}
// 检查token格式是否正确
if (!isValidToken(accessToken) || !isValidToken(refreshToken)) {
console.log('token格式不正确清除token');
console.log('Token格式不正确清除无效token');
clearAuthTokens();
return false;
}
try {
console.log('正在验证登录状态...');
// 确保accessToken有Bearer前缀
const tokenWithPrefix = accessToken.startsWith('Bearer ') ? accessToken : `Bearer ${accessToken}`;
// 使用axios直接发送请求避免request拦截器中可能的循环依赖
const response = await axios.post('/user/check-login', null, {
headers: {
'Authorization': tokenWithPrefix,
'Authorization': `Bearer ${accessToken}`,
'X-Refresh-Token': refreshToken
},
// 设置超时时间,避免长时间等待
timeout: 5000
});
if (response.data && response.data.code === 200 && response.data.data) {
console.log('登录状态有效');
// 获取用户数据
const userData = response.data.data;
console.log('登录状态有效,用户信息:', response.data.data);
// 更新用户状态
const userStore = useUserStore();
// 使用后端返回的用户数据更新前端状态
userStore.login({
userid: userData.userId,
userName: userData.username,
avatar: userData.avatar,
role: userData.role || 1,
accessToken: userData.accessToken || accessToken,
refreshToken: userData.refreshToken || refreshToken
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 (userData.accessToken) {
console.log('更新accessToken');
localStorage.setItem('accessToken', userData.accessToken);
}
if (userData.refreshToken) {
console.log('更新refreshToken');
localStorage.setItem('refreshToken', userData.refreshToken);
}
// 获取完整的用户信息
try {
// 确保使用最新的token
const currentAccessToken = userData.accessToken || accessToken;
const tokenWithPrefix = currentAccessToken.startsWith('Bearer ') ? currentAccessToken : `Bearer ${currentAccessToken}`;
const userInfoResponse = await axios.get('/user/info/getuserinfo', {
headers: {
'Authorization': tokenWithPrefix,
'X-Refresh-Token': userData.refreshToken || refreshToken
if (response.data.data.accessToken) {
localStorage.setItem('accessToken', response.data.data.accessToken);
}
});
if (userInfoResponse.data && userInfoResponse.data.code === 200 && userInfoResponse.data.data) {
const fullUserInfo = userInfoResponse.data.data;
// 更新用户完整信息
userStore.updateUserInfo({
userid: fullUserInfo.id || userData.userId,
username: fullUserInfo.username,
avatar: fullUserInfo.avatar,
email: fullUserInfo.email,
phone: fullUserInfo.phone,
college: fullUserInfo.college,
gender: fullUserInfo.gender,
role: fullUserInfo.role,
status: fullUserInfo.status
});
// 确保localStorage中也有完整的用户信息
localStorage.setItem('userId', fullUserInfo.id || userData.userId);
localStorage.setItem('username', fullUserInfo.username);
localStorage.setItem('avatar', fullUserInfo.avatar || '');
localStorage.setItem('role', fullUserInfo.role);
localStorage.setItem('email', fullUserInfo.email || '');
localStorage.setItem('phone', fullUserInfo.phone || '');
localStorage.setItem('college', fullUserInfo.college || '');
localStorage.setItem('gender', fullUserInfo.gender !== undefined ? String(fullUserInfo.gender) : '0');
}
} catch (infoError) {
console.error('获取用户完整信息失败:', infoError);
// 获取用户完整信息失败,但不影响登录状态
if (response.data.data.refreshToken) {
localStorage.setItem('refreshToken', response.data.data.refreshToken);
}
return true;
@ -124,15 +68,11 @@ export async function checkLoginStatus() {
return false;
}
} catch (error) {
console.error('验证登录状态时发生错误:', error);
if (error.response) {
console.error('错误响应:', error.response.status, error.response.data);
}
console.error('验证登录状态失败:', error);
// 如果是网络错误、超时或服务器未响应保留token并尝试从localStorage恢复
if (!error.response || error.code === 'ECONNABORTED') {
console.log('网络错误或服务器未响应');
console.log('网络错误或服务器未响应尝试从localStorage恢复登录状态');
// 如果localStorage中有用户信息则维持登录状态
const userId = localStorage.getItem('userId');
@ -148,12 +88,9 @@ export async function checkLoginStatus() {
avatar: avatar || '',
role: role || 1,
accessToken: accessToken,
refreshToken: refreshToken,
email: localStorage.getItem('email') || '',
phone: localStorage.getItem('phone') || '',
college: localStorage.getItem('college') || '',
gender: localStorage.getItem('gender') ? parseInt(localStorage.getItem('gender')) : 0
refreshToken: refreshToken
});
console.log('从localStorage成功恢复用户登录状态:', username);
return true;
}

@ -2,7 +2,6 @@ import axios from 'axios';
import { clearAuthTokens } from './auth';
import { ElMessage } from 'element-plus';
// 创建axios实例
const request = axios.create({
timeout: 5000
});
@ -48,6 +47,8 @@ request.interceptors.response.use(
return Promise.reject(response.data);
},
error => {
console.error('请求错误:', error);
// 如果响应状态码是401或500可能是token过期或格式错误
if (error.response && (error.response.status === 401 ||
(error.response.status === 500 && error.response.data?.message?.includes('token')))) {
@ -58,6 +59,7 @@ request.interceptors.response.use(
// 如果是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('登录已失效,请重新登录');
@ -86,29 +88,21 @@ request.interceptors.response.use(
return Promise.reject(error);
}
// 确保accessToken有Bearer前缀
const tokenWithPrefix = accessToken.startsWith('Bearer ') ? accessToken : `Bearer ${accessToken}`;
console.log('尝试刷新token...');
// 调用check-login接口刷新token
return axios.post('/user/check-login', null, {
headers: {
'Authorization': tokenWithPrefix,
'Authorization': `Bearer ${accessToken}`,
'X-Refresh-Token': refreshToken
}
}).then(res => {
if (res.data && res.data.code === 200 && res.data.data) {
console.log('Token刷新成功');
// 更新token
const { accessToken, refreshToken } = res.data.data;
localStorage.setItem('accessToken', accessToken);
localStorage.setItem('refreshToken', refreshToken);
// 确保新token有Bearer前缀
const newTokenWithPrefix = accessToken.startsWith('Bearer ') ? accessToken : `Bearer ${accessToken}`;
// 更新原始请求的Authorization头
originalRequest.headers['Authorization'] = newTokenWithPrefix;
originalRequest.headers['Authorization'] = `Bearer ${accessToken}`;
originalRequest.headers['X-Refresh-Token'] = refreshToken;
// 处理队列中的请求
@ -117,7 +111,6 @@ request.interceptors.response.use(
// 重新发送原始请求
return axios(originalRequest);
} else {
console.warn('Token刷新失败:', res.data);
processQueue(new Error('刷新Token失败'), null);
if (needAuth) {
// 不清除token只提示用户
@ -126,7 +119,6 @@ request.interceptors.response.use(
return Promise.reject(error);
}
}).catch(err => {
console.error('Token刷新出错:', err);
processQueue(err, null);
if (needAuth) {
// 只有在确认token格式错误时才清除
@ -145,9 +137,7 @@ request.interceptors.response.use(
return new Promise((resolve, reject) => {
requestsQueue.push({
resolve: token => {
// 确保token有Bearer前缀
const tokenWithPrefix = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
originalRequest.headers['Authorization'] = tokenWithPrefix;
originalRequest.headers['Authorization'] = `Bearer ${token}`;
resolve(axios(originalRequest));
},
reject: err => {
@ -165,6 +155,7 @@ request.interceptors.response.use(
//请求拦截器
request.interceptors.request.use(
config => {
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')) {
@ -173,14 +164,13 @@ request.interceptors.request.use(
// 检查token格式是否正确
if (token && refreshToken && typeof token === 'string' && typeof refreshToken === 'string') {
// 确保添加Bearer前缀
config.headers['Authorization'] = token.startsWith('Bearer ') ? token : `Bearer ${token}`;
config.headers['Authorization'] = `Bearer ${token}`;
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');
console.warn('Token格式不正确清除token');
clearAuthTokens();
} else if (!token || !refreshToken) {
// 如果没有token但访问需要认证的API尝试从localStorage恢复

@ -15,7 +15,6 @@
<img v-if="previewAvatar" :src="previewAvatar" alt="新头像预览" class="new-avatar">
</div>
<label class="upload-btn">
<span class="plus-icon">+</span>
选择新头像
<input type="file" accept="image/*" @change="handleAvatarChange" class="file-input">
</label>
@ -144,14 +143,14 @@ import { ref, computed, onMounted } from 'vue';
import { useUserStore } from '@/stores/user';
import request from '@/utils/request';
import { ElMessage } from 'element-plus';
import { useRouter } from 'vue-router';
import { useRouter } from 'vue-router'
const userStore = useUserStore();
const userInfo = computed(() => userStore.userInfo);
const router = useRouter();
//
const defaultAvatar = require('@/assets/default-avatar/boy_1.png');
const defaultAvatar = require('@/assets/default-avatar/boy_4.png');
//
const handleAvatarError = "this.onerror=null;this.src='" + defaultAvatar + "'";
@ -385,10 +384,9 @@ onMounted(() => {
max-width: 700px;
margin: 2rem auto;
padding: 2.5rem 2rem 2rem 2rem;
background: #fff;
background: #f8f9fa;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
position: relative;
box-shadow: 0 4px 16px rgba(52, 152, 219, 0.08);
}
.title {
@ -398,8 +396,6 @@ onMounted(() => {
margin-bottom: 2.2rem;
letter-spacing: 1px;
text-align: center;
border-bottom: 2px solid #ff88aa;
padding-bottom: 10px;
}
.section-title {
@ -432,15 +428,13 @@ onMounted(() => {
}
.new-avatar {
border-color: #ff88aa;
border-color: #3498db;
}
.upload-btn {
display: flex;
align-items: center;
gap: 8px;
display: inline-block;
padding: 0.5rem 1.2rem;
background: #ff88aa;
background: linear-gradient(90deg, #3498db 60%, #6dd5fa 100%);
color: white;
border-radius: 4px;
cursor: pointer;
@ -451,12 +445,7 @@ onMounted(() => {
}
.upload-btn:hover {
background: #ff6699;
}
.plus-icon {
font-size: 20px;
font-weight: bold;
background: linear-gradient(90deg, #2980b9 60%, #3498db 100%);
}
.file-input {
@ -494,17 +483,17 @@ onMounted(() => {
.input, .textarea, select.input {
width: 100%;
padding: 0.75rem;
border: 1px solid #e0e0e0;
border-radius: 8px;
border: 1px solid #bdc3c7;
border-radius: 4px;
font-size: 0.95rem;
transition: border-color 0.3s ease;
background: #f9f9f9;
background: #fff;
}
.input:focus, .textarea:focus, select.input:focus {
outline: none;
border-color: #ff88aa;
box-shadow: 0 0 0 2px rgba(255, 136, 170, 0.08);
border-color: #3498db;
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.08);
}
.input-error {
@ -525,10 +514,10 @@ onMounted(() => {
.submit-btn {
width: 100%;
padding: 0.85rem;
background: #ff88aa;
background: linear-gradient(90deg, #3498db 60%, #6dd5fa 100%);
color: white;
border: none;
border-radius: 8px;
border-radius: 4px;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
@ -538,34 +527,6 @@ onMounted(() => {
}
.submit-btn:hover {
background: #ff6699;
}
/* 花瓣动画(与背景呼应) */
@keyframes fall {
0% { transform: translateY(-100vh) rotate(0deg); opacity: 1; }
100% { transform: translateY(100vh) rotate(360deg); opacity: 0.5; }
}
.petal {
position: absolute;
width: 10px;
height: 15px;
background: pink;
border-radius: 50% 50% 0 0;
animation: fall 5s linear infinite;
z-index: -1;
}
/* 初始化花瓣可在mounted中动态生成此处简化 */
.change-info-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
animation: none; /* 避免重复动画 */
background: linear-gradient(90deg, #2980b9 60%, #3498db 100%);
}
</style>

@ -1,12 +1,17 @@
<template>
<div>
<div class="top-container">
<NoticeBoard class="notice-flex"/>
<WelcomeCalendar class="calendar-flex"/>
<NoticeBoard />
<WelcomeCalendar />
</div>
<div class="post-container">
<PostPage />
</div>
<!-- <UserPage /> -->
<!-- <PostDetail /> -->
</div>
</template>
@ -14,29 +19,33 @@
import NoticeBoard from '@/components/NoticeBoard.vue';
import WelcomeCalendar from '@/components/WelcomeCalendar.vue';
import PostPage from '@/components/PostPage.vue';
// import UserPage from './UserPage.vue';
// import PostDetail from './PostDetail.vue';
</script>
<style scoped>
.top-container {
display: flex;
align-items: stretch;
gap: 16px;
width: 100%;
max-width: 1000px;
height: 300px;
margin: 0 auto;
justify-content: center; /* 居中排列 */
align-items: center; /* 顶部对齐 */
padding: 10px;
gap: 10px; /* 设置较小的间距 */
width: 820px; /* 固定宽度 */
height: 350px; /* 固定高度 */
margin:20px auto 0; /* 上边距20px左右居中 */
overflow: hidden; /* 防止溢出 */
box-sizing: border-box; /* 包括内边距在宽高内 */
}
.notice-flex, .calendar-flex {
height: 100%;
.top-container > * {
flex-shrink: 0;
}
.post-container {
display: flex;
justify-content: center;
align-items: flex-start;
justify-content: center; /* 居中排列 */
align-items: center; /* 顶部对齐 */
padding: 10px;
width: 100%;
max-width: 1000px;
margin: 10px auto;
box-sizing: border-box;
width: 820px; /* 固定宽度 */
margin: 20px auto; /* 上边距20px左右居中 */
box-sizing: border-box; /* 包括内边距在宽高内 */
}
</style>

@ -1,37 +1,28 @@
<template>
<div class="main-content">
<!-- 帖子内容 -->
<div class="post-content">
<div class="author-row">
<div class="post-detail-container">
<!-- 作者信息栏 -->
<div class="author-info" v-if="author && author.userName">
<img
:src="(author.userName === '匿名用户' || postDetailStore.post?.status === 1 )
? require('@/assets/default-avatar/boy_4.png')
: author.userAvatar|| require('@/assets/default-avatar/boy_1.png') "
:src="author.userAvatar || require('@/assets/default-avatar/boy_1.png')"
alt="头像"
class="author-avatar"
:style="{ cursor: (author.userName === '匿名用户' || postDetailStore.post?.status === 1 ) ? 'not-allowed' : 'pointer' }"
@click="handleAuthorAvatarClick"
@click="goUserHome(author.userId)"
style="cursor: pointer;"
/>
<div class="author-info">
<div class="author-details">
<h3 class="author-name">{{ author.userName || '匿名用户' }}</h3>
<p class="author-stats">粉丝数{{ author.followers ?? 0 }}</p>
<button @click="toggleFollow" class="follow-button">
{{ isFollowing ? '取消关注' : '关注' }}
</button>
</div>
<h1 class="post-title">{{ postDetailStore.post?.title || '' }}</h1>
</div>
<!-- 帖子内容 -->
<div class="post-content">
<img :src="author?.userAvatar || require('@/assets/default-avatar/boy_1.png')" alt="作者头像" class="post-author-avatar" />
<h1 class="post-title">{{ postDetailStore.post?.title || '' }}</h1>
<p class="post-body">{{ postDetailStore.post?.content || '' }}</p>
<img
v-if="postDetailStore.post?.image"
:src="postDetailStore.post.image"
alt="帖子封面"
class="post-cover"
@click="showImagePreview = true"
/>
<!-- 大图预览遮罩 -->
<div v-if="showImagePreview" class="image-preview-mask" @click="showImagePreview = false">
<img :src="postDetailStore.post.image" class="image-preview-big" @click.stop />
</div>
<div class="post-stats">
<span> 👁 {{ postDetailStore.post?.viewCount ?? 0 }}</span>
<span> 🗨 {{ postDetailStore.post?.commentCount ?? 0 }}</span>
@ -51,13 +42,11 @@
发布时间{{ postDetailStore.post?.createTime ? formatTime(postDetailStore.post.createTime) : '' }}
</div>
</div>
<!-- 评论区 -->
<div class="comments-section">
<h2 class="comments-title">评论</h2>
<ul class="comments-list">
<li v-if="postDetailStore.comments.length === 0" class="comments-finished-tip">
暂时没有评论哦快来发表吧
</li>
<li v-for="comment in postDetailStore.comments" :key="comment.id" class="comment-item">
<img
:src="comment.userAvatar || require('@/assets/default-avatar/boy_1.png')"
@ -69,19 +58,8 @@
<div class="comment-content">
<p class="comment-name">{{ comment.userName || '匿名用户' }}</p>
<p class="comment-text">{{ comment.content || '' }}</p>
<button
class="delete-btn"
v-if="String(comment.userId) === String(userStore.userInfo?.userid)"
@click="handleDelete(comment)"
></button>
<div class="comment-meta">
<span class="comment-time">{{ comment.createTime ? formatTime(comment.createTime) : '' }}</span>
<button class="reply-btn" @click="startReply(comment)"></button>
<span v-if="comment.replyCount > 0">
<button @click="loadReplies(comment)" class="expand-reply-btn">
{{ comment.showReplies> 0 ? '收起回复' : '展开回复' }} ({{ comment.replyCount }})
</button>
</span>
<button
@click="postDetailStore.CommentLike(comment)"
:class="['like-btn', { liked: comment.isLike }]"
@ -90,6 +68,17 @@
<span v-else></span>
{{ comment.likeCount ?? 0 }}
</button>
<span v-if="comment.replyCount > 0">
<button @click="loadReplies(comment)">
{{ comment.showReplies> 0 ? '收起回复' : '展开回复' }} ({{ comment.replyCount }})
</button>
</span>
<button class="reply-btn" @click="startReply(comment)"></button>
<button
class="delete-btn"
v-if="String(comment.userId) === String(userStore.userInfo?.userid)"
@click="handleDelete(comment)"
>删除</button>
</div>
<!-- 子评论列表 -->
<ul v-if="comment.showReplies && comment.replies && comment.replies.length > 0" class="replies-list">
@ -107,14 +96,8 @@
<span v-if="reply.replyUserName" class="reply-user"> @{{ reply.replyUserName }}</span>
</p>
<p class="comment-text">{{ reply.content || '' }}</p>
<button
class="delete-btn"
v-if="String(reply.userId) === String(userStore.userInfo?.userid)"
@click="handleDelete(reply)"
></button>
<div class="comment-meta">
<span class="comment-time">{{ reply.createTime ? formatTime(reply.createTime) : '' }}</span>
<button class="reply-btn" @click="startReply(reply)"></button>
<button
@click="postDetailStore.CommentLike(reply)"
:class="['like-btn', { liked: reply.isLike }]"
@ -123,6 +106,12 @@
<span v-else></span>
{{ reply.likeCount ?? 0 }}
</button>
<button class="reply-btn" @click="startReply(reply)"></button>
<button
class="delete-btn"
v-if="String(reply.userId) === String(userStore.userInfo?.userid)"
@click="handleDelete(reply)"
>删除</button>
</div>
</div>
</li>
@ -131,9 +120,6 @@
</ul>
</div>
</li>
<li v-if="postDetailStore.comments.length > 0 && postDetailStore.commentsFinished" class="comments-finished-tip">
没有更多评论啦......
</li>
</ul>
</div>
@ -143,14 +129,12 @@
正在回复 @{{ replyingComment.userName || '匿名用户' }}
<button class="cancel-reply-btn" @click="cancelReply"></button>
</div>
<div style="position:relative;">
<textarea
v-model="newComment"
placeholder="写下你的评论..."
class="comment-input"
></textarea>
<button @click="sendComment" class="send-button" style="position:absolute; right:16px; bottom:16px;">发送</button>
</div>
<button @click="sendComment" class="send-button">发送</button>
</div>
</div>
</template>
@ -158,7 +142,7 @@
<script setup lang="js" name="PostDetail">
import { ref, computed, onMounted , onUnmounted, watch } from 'vue';
import { useRoute,useRouter } from 'vue-router';
import { ElMessageBox, ElMessage } from 'element-plus';
import { ElMessageBox } from 'element-plus';
import { usePostDetailStore } from '@/stores/postdetail.js';
import { useUserStore } from '@/stores/user.js';
@ -168,7 +152,7 @@ const postDetailStore = usePostDetailStore();
const userStore = useUserStore();
const newComment = ref('');
const replyingComment = ref(null);
const showImagePreview = ref(false);
// author undefined
const author = computed(() => postDetailStore.post?.author || {});
@ -254,19 +238,6 @@ function handleDelete(comment) {
postDetailStore.deleteComment(comment);
}).catch(() => {});
}
function handleAuthorAvatarClick() {
//
if (
(author.value.userName === '匿名用户' || postDetailStore.post?.status === 1) &&
(!author.value.userAvatar || author.value.userAvatar === '')
) {
ElMessage.info('该用户为匿名用户');
return;
}
goUserHome(author.value.userId);
}
// postDetailStore.post
watch(
() => postDetailStore.post,
@ -288,32 +259,30 @@ onUnmounted(() => {
</script>
<style scoped>
.main-content {
max-width: 800px;
margin: 0 auto;
display: flex;
flex-direction: column;
height: 100%;
.post-detail-container {
display: grid;
grid-template-areas:
"post-content author-info"
"comments-section author-info"
"comment-box author-info";
grid-template-columns: 3fr 1fr; /* 左侧内容占 3/4右侧作者信息占 1/4 */
gap: 20px;
min-width: 0;
min-height: 0;
background: transparent;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
}
.author-row {
display: flex;
align-items: center;
margin-bottom: 5px;
padding: 20px;
box-sizing: border-box;
max-width: 1200px; /* 设置页面最大宽度 */
margin: 0 auto; /* 居中页面并添加左右留白 */
}
.author-info {
grid-area: author-info;
display: flex;
flex-direction: column;
flex-direction: row; /* 水平排列头像和详情 */
align-items: flex-start;
min-width: 90px;
margin-right: 18px;
background-color: #f5f5f5;
padding: 20px;
border: 1px solid #ccc;
max-height: 200px;
border-radius: 8px;
}
.author-avatar {
@ -324,11 +293,24 @@ onUnmounted(() => {
margin-right: 15px; /* 与作者详情的间距 */
}
.author-details {
display: flex;
flex-direction: column;
align-items: flex-start;
}
.author-name {
font-size: 28px;
font-size: 18px;
margin: 0;
}
.author-stats {
font-size: 14px;
color: #666;
margin: 5px 0;
text-align: left; /* 设置居左 */
}
.follow-button {
margin-top: 10px;
padding: 8px 16px;
@ -347,64 +329,33 @@ onUnmounted(() => {
.post-content {
grid-area: post-content;
background-color: #ffffff;
padding: 5px;
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
position: relative;
position: relative; /* 为绝对定位的元素提供参考 */
}
.post-cover {
position: static;
display: block;
margin: 5px auto 0 auto;
width: auto;
height: 200px;
max-width: 100%;
.post-author-avatar {
position: absolute;
top: 20px;
right: 20px;
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
border-radius: 12px;
border: 2px solid #ccc;
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
cursor: pointer; /* 鼠标悬停变小手 */
transition: box-shadow 0.2s;
}
.post-cover:hover {
box-shadow: 0 4px 24px rgba(0,0,0,0.18);
}
.image-preview-mask {
position: fixed;
z-index: 9999;
inset: 0;
background: rgba(0,0,0,0.7);
display: flex;
align-items: center;
justify-content: center;
}
.image-preview-big {
max-width: 90vw;
max-height: 90vh;
border-radius: 12px;
box-shadow: 0 4px 32px rgba(0,0,0,0.25);
background: #fff;
}
.post-title {
font-size: 32px;
font-size: 24px;
margin-bottom: 10px;
text-align: left;
padding-left: 5px;
word-break: break-all; /* 长单词或长串自动换行 */
white-space: normal;
text-align: left; /* 设置居左 */
}
.post-body {
font-size: 20px;
font-size: 16px;
line-height: 1.5;
margin-bottom: 20px;
text-align: left;
padding-left: 16px;
word-break: break-all; /* 长单词或长串自动换行 */
white-space: pre-line;
}
.post-stats {
@ -418,20 +369,18 @@ onUnmounted(() => {
.post-time {
position: absolute;
bottom: 5px; /* 距离底部 20px */
bottom: 20px; /* 距离底部 20px */
right: 20px; /* 距离右侧 20px */
font-size: 12px;
color: #999;
}
.comments-section {
flex: 1 1 0;
min-height: 400px;
grid-area: comments-section;
background-color: #ffffff;
padding: 20px;
border: 1px solid #ccc;
border-radius: 8px;
overflow-y: auto;
}
.comments-title {
@ -467,7 +416,6 @@ onUnmounted(() => {
flex: 1;
display: flex;
flex-direction: column;
padding-left: 12px;
}
.comment-name {
@ -479,9 +427,6 @@ onUnmounted(() => {
margin: 0;
font-size: 14px;
color: #333;
text-align: left;
white-space: pre-line; /* 保留换行 */
word-break: break-all; /* 长单词或长串自动换行 */
}
.comment-meta {
@ -496,50 +441,32 @@ onUnmounted(() => {
font-size: 12px;
color: #666;
}
.expand-reply-btn {
background: none;
border: none;
color: #409eff;
cursor: pointer;
font-size: 13px;
padding: 2px 8px;
z-index: 1;
}
.comment-likes {
font-size: 12px;
color: #666;
}
.comments-finished-tip {
color: #999;
font-size: 14px;
text-align: center;
padding: 16px 0 8px 0;
letter-spacing: 1px;
}
.comment-box {
grid-area: comment-box;
display: flex;
flex-direction: column;
gap: 10px;
background: #fff;
padding: 6px 24px 12px 24px;
box-shadow: 0 -2px 12px rgba(0,0,0,0.06);
border-radius: 8px;
}
.comment-input {
width: 100%;
height: 80px;
padding: 10px 80px 32px 10px; /* 右下留空间给按钮 */
padding: 10px;
border: 1px solid #ccc;
border-radius: 8px;
resize: none;
font-size: 14px;
box-sizing: border-box;
}
.send-button {
/* 位置已由内联style控制 */
padding: 8px 20px;
align-self: flex-end;
padding: 10px 20px;
background-color: #5aa76f;
color: white;
border: none;
@ -563,7 +490,7 @@ onUnmounted(() => {
.replies-list {
list-style: none;
padding-left: 48px;
padding-left: 48px; /* 更明显的缩进 */
margin: 8px 0 0 0;
border-left: 2px solid #e0e0e0;
}
@ -631,28 +558,21 @@ onUnmounted(() => {
border: none;
color: #f56c6c;
cursor: pointer;
font-size: 18px;
font-size: 13px;
margin-left: 10px;
position: absolute;
top: 0;
right: 0;
z-index: 2;
}
.delete-btn:hover {
color: #ff2222;
background: #f9eaea;
border-radius: 50%;
text-decoration: underline;
}
.like-btn {
margin-left: 8px;
background: none;
border: none;
color: #222323;
color: #409eff;
cursor: pointer;
font-size: 14px;
}
.like-btn.liked {
color: #f56c6c;
}
</style>

@ -12,51 +12,28 @@
maxlength="50"
placeholder="请输入标题3-50个字符"
required
class="input-field"
/>
</div>
<!-- 分类选择 -->
<div class="form-row">
<label>分类</label>
<select v-model="form.categoryId" required class="input-field">
<select v-model="form.categoryId" required>
<option disabled value="">请选择分类</option>
<option v-for="cat in categories" :key="cat.id" :value="cat.id">{{ cat.name }}</option>
</select>
</div>
<!-- 图片上传自定义+号按钮 -->
<!-- 图片上传 -->
<div class="form-row">
<label>封面</label>
<div class="upload-wrapper">
<!-- 隐藏的文件选择input -->
<input
type="file"
accept="image/*"
@change="onFileChange"
id="image-upload"
class="hidden-input"
/>
<!-- 自定义+号按钮 -->
<label for="image-upload" class="upload-btn">
<span class="plus-icon">+</span>
</label>
<!-- 图片预览 -->
<label>图片</label>
<input type="file" accept="image/*" @change="onFileChange" />
<div v-if="form.imagePreview" class="img-preview">
<img :src="form.imagePreview" alt="预览" class="preview-img" />
<button type="button" @click="removeImage" class="remove-btn">×</button>
</div>
<img :src="form.imagePreview" alt="预览" />
<button type="button" @click="removeImage"></button>
</div>
</div>
<!-- 匿名发布选项 -->
<div class="form-row anonymous-option">
<label class="anonymous-label">
<input type="checkbox" v-model="form.status" true-value="1" false-value="0" class="anonymous-checkbox" />
匿名发布
</label>
</div>
<!-- 内容编辑器 -->
<div class="form-row">
<label>内容</label>
@ -65,13 +42,12 @@
rows="8"
placeholder="请输入帖子内容支持Markdown语法"
required
class="textarea-field"
></textarea>
</div>
<!-- 提交按钮 -->
<div class="submit-area">
<button type="submit" :disabled="submitting" class="submit-btn">
<button type="submit" :disabled="submitting">
{{ submitting ? '发布中...' : '立即发布' }}
</button>
</div>
@ -95,10 +71,9 @@ export default {
const router = useRouter()
const submitting = ref(false)
const categories = ref([
{ id: 1, name: '校园活动' },
{ id: 2, name: '学习' },
{ id: 3, name: '娱乐' },
{ id: 4, name: '二手交易' }
{ id: 1, name: '学习' },
{ id: 2, name: '娱乐' },
{ id: 3, name: '二手交易' }
])
const form = ref({
title: '',
@ -118,7 +93,9 @@ export default {
const onFileChange = async (e) => {
const file = e.target.files[0]
if (!file) return
//
form.value.imagePreview = URL.createObjectURL(file)
//
const fd = new FormData()
fd.append('file', file)
try {
@ -146,10 +123,6 @@ export default {
showResult('error', '请填写完整信息')
return
}
if (!form.value.image) {
showResult('error', '请先选择帖子封面')
return
}
if (form.value.title.length < 3 || form.value.title.length > 50) {
showResult('error', '标题长度需3-50字符')
return
@ -170,6 +143,7 @@ export default {
const res = await request.post('/post', postData)
if (res.code === 200) {
showResult('success', '帖子发布成功')
console.log(res.data);
setTimeout(() => {
router.push(`/`)
}, 1200)
@ -204,181 +178,94 @@ export default {
</script>
<style scoped>
.upload-wrapper {
display: flex;
align-items: center;
gap: 15px;
}
.hidden-input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.upload-btn {
width: 80px;
height: 80px;
border: 2px dashed #ff88aa;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
}
.upload-btn:hover {
background-color: #fff5f8;
border-color: #ff6699;
}
.plus-icon {
font-size: 32px;
color: #ff88aa;
font-weight: lighter;
}
.img-preview {
position: relative;
display: inline-block;
}
.preview-img {
width: 80px;
height: 80px;
object-fit: cover;
border-radius: 8px;
border: 1px solid #eee;
}
.remove-btn {
position: absolute;
top: -8px;
right: -8px;
width: 20px;
height: 20px;
background: #ff6699;
color: white;
border: none;
border-radius: 50%;
padding: 0;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
}
.post-container {
max-width: 600px;
margin: 30px auto;
padding: 30px;
background: #fff;
border-radius: 12px;
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.08);
}
.page-title {
text-align: center;
margin-bottom: 24px;
font-size: 1.8em;
font-size: 1.6em;
color: #2c3e50;
border-bottom: 2px solid #ff88aa;
padding-bottom: 10px;
}
.editor-wrapper {
padding: 20px;
}
.form-row {
margin-bottom: 20px;
margin-bottom: 18px;
display: flex;
flex-direction: column;
}
.form-row label {
font-weight: bold;
margin-bottom: 8px;
color: #333;
}
.input-field,
.textarea-field {
padding: 10px;
border: 1px solid #e0e0e0;
border-radius: 8px;
margin-bottom: 6px;
}
.form-row input[type="text"],
.form-row select,
.form-row textarea {
padding: 8px;
border: 1px solid #dcdcdc;
border-radius: 4px;
font-size: 15px;
transition: border-color 0.3s;
}
.input-field:focus,
.textarea-field:focus {
outline: none;
border-color: #ff88aa;
}
.anonymous-option {
flex-direction: row;
align-items: center;
justify-content: flex-start;
margin-bottom: 15px;
.form-row textarea {
resize: vertical;
}
.anonymous-label {
display: flex;
align-items: center;
gap: 8px;
color: #666;
.img-preview {
margin-top: 8px;
position: relative;
display: inline-block;
}
.anonymous-checkbox {
width: 18px;
height: 18px;
accent-color: #ff88aa;
.img-preview img {
max-width: 120px;
max-height: 120px;
border-radius: 4px;
border: 1px solid #eee;
}
.textarea-field {
resize: vertical;
min-height: 150px;
.img-preview button {
position: absolute;
top: 2px;
right: 2px;
background: #f56c6c;
color: #fff;
border: none;
border-radius: 3px;
padding: 2px 8px;
cursor: pointer;
font-size: 12px;
}
.submit-area {
text-align: center;
margin-top: 30px;
margin-top: 24px;
}
.submit-btn {
background: #ff88aa;
color: white;
.submit-area button {
background: #409eff;
color: #fff;
border: none;
padding: 12px 40px;
border-radius: 8px;
padding: 10px 32px;
border-radius: 4px;
font-size: 16px;
cursor: pointer;
transition: background 0.3s;
}
.submit-btn:disabled {
background: #ffcccc;
.submit-area button:disabled {
background: #b3d8ff;
cursor: not-allowed;
}
.submit-btn:hover {
background: #ff6699;
}
.result-alert {
margin-top: 20px;
padding: 15px 20px;
border-radius: 8px;
padding: 12px 18px;
border-radius: 4px;
font-size: 15px;
text-align: center;
}
.result-alert.success {
background: #e1f3d8;
color: #3a7a1c;
}
.result-alert.error {
background: #fde2e2;
color: #c0392b;

@ -226,22 +226,15 @@ const loadUserInfo = async () => {
if (isCurrentUser.value) {
const storedUsername = localStorage.getItem('username');
const storedAvatar = localStorage.getItem('avatar');
const storedEmail = localStorage.getItem('email');
const storedPhone = localStorage.getItem('phone');
const storedCollege = localStorage.getItem('college');
const storedGender = localStorage.getItem('gender');
if (storedUsername) {
userInfo.value = {
username: storedUsername,
avatar: storedAvatar || '',
role: localStorage.getItem('role') || 1,
userId: localStorage.getItem('userId'),
email: storedEmail || '',
phone: storedPhone || '',
college: storedCollege || '',
gender: storedGender ? parseInt(storedGender) : 0
userId: localStorage.getItem('userId')
};
console.log('从localStorage恢复用户基本信息:', storedUsername);
}
}
@ -252,24 +245,10 @@ const loadUserInfo = async () => {
timeout: 5000
});
console.log('获取用户信息响应');
console.log('获取用户信息响应:', response);
if (response && response.code === 200) {
userInfo.value = response.data;
// userStore
if (isCurrentUser.value && userStore) {
userStore.updateUserInfo({
userid: response.data.id,
username: response.data.username,
avatar: response.data.avatar,
email: response.data.email,
phone: response.data.phone,
college: response.data.college,
gender: response.data.gender,
role: response.data.role,
status: response.data.status
});
}
console.log('加载到的用户信息:', userInfo.value);
//
if (isCurrentUser.value) {
@ -281,17 +260,14 @@ const loadUserInfo = async () => {
// 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,
email: userStore.userInfo.email || '',
phone: userStore.userInfo.phone || '',
college: userStore.userInfo.college || '',
gender: userStore.userInfo.gender || 0
userId: userStore.userInfo.userid
};
} else {
//
@ -311,7 +287,7 @@ const loadUserPosts = async () => {
...pageParams
}
});
console.log('加载用户帖子响应');
console.log('加载用户帖子响应:', response.data.records);
if (response && response.code === 200) {
const newPosts = response.data.records || [];
userPosts.value = [...userPosts.value, ...newPosts];

Loading…
Cancel
Save