From b286e36ab04d89da8ef402032f4cc5c07669bf91 Mon Sep 17 00:00:00 2001 From: shenjianZ Date: Tue, 13 Jan 2026 17:38:01 +0800 Subject: [PATCH] feat: fix the time display --- .../newsclassifier/config/JacksonConfig.java | 34 ++ .../aisi/newsclassifier/domain/RestCode.java | 4 +- .../domain/dto/NewsCreateDto.java | 2 + .../newsclassifier/domain/dto/NewsDto.java | 3 + .../domain/dto/NewsQueryDto.java | 2 + .../domain/dto/NewsUpdateDto.java | 2 + .../newsclassifier/domain/dto/UserDto.java | 3 + .../newsclassifier/domain/entity/News.java | 4 +- .../newsclassifier/domain/entity/User.java | 4 +- .../domain/enums/ErrorCode.java | 25 ++ .../aisi/newsclassifier/domain/vo/NewsVo.java | 4 +- .../aisi/newsclassifier/domain/vo/UserVo.java | 3 + .../exception/BusinessException.java | 39 +++ .../handler/GlobalExceptionHandler.java | 124 ++++++- .../src/main/resources/application-dev.yaml | 2 +- client/src/App.vue | 15 +- client/src/api/news.ts | 3 +- client/src/router/index.ts | 6 + client/src/views/auth/LoginView.vue | 18 +- client/src/views/auth/RegisterView.vue | 315 ++++++++++++++++++ client/src/views/home/DashboardView.vue | 2 +- 21 files changed, 595 insertions(+), 19 deletions(-) create mode 100644 backend/src/main/java/com/aisi/newsclassifier/config/JacksonConfig.java create mode 100644 backend/src/main/java/com/aisi/newsclassifier/domain/enums/ErrorCode.java create mode 100644 backend/src/main/java/com/aisi/newsclassifier/exception/BusinessException.java create mode 100644 client/src/views/auth/RegisterView.vue diff --git a/backend/src/main/java/com/aisi/newsclassifier/config/JacksonConfig.java b/backend/src/main/java/com/aisi/newsclassifier/config/JacksonConfig.java new file mode 100644 index 0000000..ed60557 --- /dev/null +++ b/backend/src/main/java/com/aisi/newsclassifier/config/JacksonConfig.java @@ -0,0 +1,34 @@ +package com.aisi.newsclassifier.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; + +@Configuration +public class JacksonConfig { + + @Bean + public ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + + // 配置日期时间格式 + JavaTimeModule javaTimeModule = new JavaTimeModule(); + + // 定义日期时间格式 + DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(dateTimeFormatter)); + + mapper.registerModule(javaTimeModule); + + // 禁用时间戳格式 + mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + + return mapper; + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/RestCode.java b/backend/src/main/java/com/aisi/newsclassifier/domain/RestCode.java index b979975..ffb8da6 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/RestCode.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/RestCode.java @@ -20,7 +20,9 @@ public enum RestCode { TOKEN_EMPTY(403, "token不能为空"), TOKEN_INVALID(403, "token非法"), SYSTEM_ERROR(500,"系统错误,请联系管理员" ), - DATA_NOT_FOUND(404,"数据不存在"); + DATA_NOT_FOUND(404,"数据不存在"), + DATA_ALREADY_FOUND(409,"数据已存在"), + METHOD_NOT_SUPPORT(405,"不支持该请求方法"); private final int code; private final String message; diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsCreateDto.java b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsCreateDto.java index 673d03d..d985f82 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsCreateDto.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsCreateDto.java @@ -1,6 +1,7 @@ package com.aisi.newsclassifier.domain.dto; import io.swagger.v3.oas.annotations.media.Schema; +import com.fasterxml.jackson.annotation.JsonFormat; import jakarta.validation.constraints.NotBlank; import lombok.Data; @@ -25,6 +26,7 @@ public class NewsCreateDto { private Integer categoryId; @Schema(description = "发布时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime publishTime; @Schema(description = "作者") diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsDto.java b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsDto.java index fefdd61..f994c14 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsDto.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsDto.java @@ -1,6 +1,7 @@ package com.aisi.newsclassifier.domain.dto; import io.swagger.v3.oas.annotations.media.Schema; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.time.LocalDateTime; @@ -28,6 +29,7 @@ public class NewsDto { private String categoryName; @Schema(description = "发布时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime publishTime; @Schema(description = "作者") @@ -40,5 +42,6 @@ public class NewsDto { private String content; @Schema(description = "入库时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime createdAt; } diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsQueryDto.java b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsQueryDto.java index 3298865..b060c93 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsQueryDto.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsQueryDto.java @@ -1,6 +1,7 @@ package com.aisi.newsclassifier.domain.dto; import io.swagger.v3.oas.annotations.media.Schema; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; /** @@ -26,6 +27,7 @@ public class NewsQueryDto { private String keyword; @Schema(description = "排序字段", example = "createdAt") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private String sortBy = "createdAt"; @Schema(description = "排序方向", example = "DESC") diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsUpdateDto.java b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsUpdateDto.java index 725f234..b303bc8 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsUpdateDto.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/NewsUpdateDto.java @@ -1,6 +1,7 @@ package com.aisi.newsclassifier.domain.dto; import io.swagger.v3.oas.annotations.media.Schema; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.time.LocalDateTime; @@ -22,6 +23,7 @@ public class NewsUpdateDto { private Integer categoryId; @Schema(description = "发布时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime publishTime; @Schema(description = "作者") diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/UserDto.java b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/UserDto.java index 196b137..0f36f9c 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/dto/UserDto.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/dto/UserDto.java @@ -1,6 +1,7 @@ package com.aisi.newsclassifier.domain.dto; import io.swagger.v3.oas.annotations.media.Schema; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.time.LocalDateTime; @@ -28,8 +29,10 @@ public class UserDto { private String role; @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime createdAt; @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime updatedAt; } diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/entity/News.java b/backend/src/main/java/com/aisi/newsclassifier/domain/entity/News.java index c5dc861..2c46102 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/entity/News.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/entity/News.java @@ -39,7 +39,7 @@ public class News { private Integer categoryId; @Column(name = "publish_time") - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime publishTime; @Column(name = "author", length = 100) @@ -56,7 +56,7 @@ public class News { private String contentHash; @Column(name = "created_at", nullable = false, updatable = false) - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime createdAt; /** diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/entity/User.java b/backend/src/main/java/com/aisi/newsclassifier/domain/entity/User.java index 78ad5a6..27eaef3 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/entity/User.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/entity/User.java @@ -42,12 +42,12 @@ public class User { @CreatedDate @Column(name = "created_at", nullable = false, updatable = false) - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime createdAt; @LastModifiedDate @Column(name = "updated_at", nullable = false) - @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime updatedAt; /** diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/enums/ErrorCode.java b/backend/src/main/java/com/aisi/newsclassifier/domain/enums/ErrorCode.java new file mode 100644 index 0000000..a5c09ef --- /dev/null +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/enums/ErrorCode.java @@ -0,0 +1,25 @@ +package com.aisi.newsclassifier.domain.enums; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public enum ErrorCode { + + // 通用错误 + PARAM_ERROR(400, "参数错误"), + SYSTEM_ERROR(500, "系统繁忙,请稍后再试"), + + // 用户相关 (1000 - 1999) + USER_NOT_FOUND(1001, "用户不存在"), + PASSWORD_ERROR(1002, "密码错误"), + USER_LOCKED(1003, "账号已被锁定"), + + // 新闻相关 (2000 - 2999) + NEWS_NOT_FOUND(2001, "请求的新闻不存在"), + CATEGORY_INVALID(2002, "无效的分类ID"); + + private final int code; + private final String message; +} \ No newline at end of file diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/vo/NewsVo.java b/backend/src/main/java/com/aisi/newsclassifier/domain/vo/NewsVo.java index b135394..ed97af1 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/vo/NewsVo.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/vo/NewsVo.java @@ -2,7 +2,7 @@ package com.aisi.newsclassifier.domain.vo; import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; - +import com.fasterxml.jackson.annotation.JsonFormat; import java.time.LocalDateTime; @Data @@ -25,6 +25,7 @@ public class NewsVo { private String categoryName; @Schema(description = "发布时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime publishTime; @Schema(description = "作者") @@ -37,5 +38,6 @@ public class NewsVo { private String content; @Schema(description = "入库时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime createdAt; } diff --git a/backend/src/main/java/com/aisi/newsclassifier/domain/vo/UserVo.java b/backend/src/main/java/com/aisi/newsclassifier/domain/vo/UserVo.java index dd7499e..b24d0ca 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/domain/vo/UserVo.java +++ b/backend/src/main/java/com/aisi/newsclassifier/domain/vo/UserVo.java @@ -1,6 +1,7 @@ package com.aisi.newsclassifier.domain.vo; import io.swagger.v3.oas.annotations.media.Schema; +import com.fasterxml.jackson.annotation.JsonFormat; import lombok.Data; import java.time.LocalDateTime; @@ -25,8 +26,10 @@ public class UserVo { private String role; @Schema(description = "创建时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime createdAt; @Schema(description = "更新时间") + @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Shanghai") private LocalDateTime updatedAt; } diff --git a/backend/src/main/java/com/aisi/newsclassifier/exception/BusinessException.java b/backend/src/main/java/com/aisi/newsclassifier/exception/BusinessException.java new file mode 100644 index 0000000..b99b157 --- /dev/null +++ b/backend/src/main/java/com/aisi/newsclassifier/exception/BusinessException.java @@ -0,0 +1,39 @@ +package com.aisi.newsclassifier.exception; +import com.aisi.newsclassifier.domain.enums.ErrorCode; + +import lombok.Getter; + +/** + * 自定义业务异常 + * 用于在 Service 层中断逻辑,并返回具体的错误码和错误信息 + */ +@Getter // 使用 Lombok 自动生成 getCode() 方法 +public class BusinessException extends RuntimeException { + + // 错误码 (例如 400, 403, 1001 等) + private final int code; + + /** + * 构造方法 1:手动指定 code 和 message + * 使用:throw new BusinessException(404, "找不到该新闻"); + */ + public BusinessException(int code, String message) { + super(message); // 把 message 传给父类,方便 log 打印 + this.code = code; + } + + /** + * 构造方法 2:使用通用错误码 (默认为 400 或 500) + * 使用:throw new BusinessException("操作失败"); + */ + public BusinessException(String message) { + super(message); + this.code = 400; // 默认给个 400 + } + + // 在 BusinessException 类里添加这个构造方法 + public BusinessException(ErrorCode errorCode) { + super(errorCode.getMessage()); + this.code = errorCode.getCode(); + } +} \ No newline at end of file diff --git a/backend/src/main/java/com/aisi/newsclassifier/handler/GlobalExceptionHandler.java b/backend/src/main/java/com/aisi/newsclassifier/handler/GlobalExceptionHandler.java index 5750766..8f4e638 100644 --- a/backend/src/main/java/com/aisi/newsclassifier/handler/GlobalExceptionHandler.java +++ b/backend/src/main/java/com/aisi/newsclassifier/handler/GlobalExceptionHandler.java @@ -4,8 +4,20 @@ import com.aisi.newsclassifier.domain.RestBean; import com.aisi.newsclassifier.domain.RestCode; import lombok.extern.slf4j.Slf4j; import org.springframework.security.authorization.AuthorizationDeniedException; + +import org.springframework.dao.DuplicateKeyException; +import org.springframework.http.converter.HttpMessageNotReadableException; +import org.springframework.validation.BindException; +import org.springframework.validation.BindingResult; +import org.springframework.web.HttpRequestMethodNotSupportedException; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.MissingServletRequestParameterException; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; +import com.aisi.newsclassifier.exception.BusinessException; + +import java.util.stream.Collectors; /** * 全局异常处理器 @@ -13,6 +25,116 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; @Slf4j @RestControllerAdvice public class GlobalExceptionHandler { + // ========================================== + // 1. 客户端请求错误 (400 Bad Request 等) + // 这类错误是前端传参不对,记录 WARN 日志,不需要打印堆栈 + // ========================================== + + /** + * 1. 参数校验失败异常 (@Valid / @Validated) + * 场景:前端传的 JSON 缺字段,或者字段不符合 @NotNull, @Size 等注解要求 + */ + @ExceptionHandler({MethodArgumentNotValidException.class, BindException.class}) + public RestBean handleValidationException(Exception e) { + BindingResult bindingResult = null; + if (e instanceof MethodArgumentNotValidException) { + bindingResult = ((MethodArgumentNotValidException) e).getBindingResult(); + } else if (e instanceof BindException) { + bindingResult = ((BindException) e).getBindingResult(); + } + + // 提取具体的错误信息(例如:"email: 邮箱格式不正确") + String msg = "参数校验失败"; + if (bindingResult != null) { + msg = bindingResult.getFieldErrors().stream() + .map(error -> error.getField() + ": " + error.getDefaultMessage()) + .collect(Collectors.joining(", ")); + } + + log.warn("参数校验未通过: {}", msg); + return RestBean.failure(RestCode.FAILURE, msg); + } + + /** + * 2. JSON 格式解析错误 + * 场景:前端传的 JSON 少了括号,或者把 String 传给了 Integer 类型的字段 + */ + @ExceptionHandler(HttpMessageNotReadableException.class) + public RestBean handleJsonParseException(HttpMessageNotReadableException e) { + log.warn("JSON解析失败: {}", e.getMessage()); + return RestBean.failure(RestCode.FAILURE, "请求Body格式错误,请检查JSON语法"); + } + + /** + * 3. 缺少必要的 URL 参数 + * 场景:接口定义了 @RequestParam(required=true) 但前端没传 + */ + @ExceptionHandler(MissingServletRequestParameterException.class) + public RestBean handleMissingParam(MissingServletRequestParameterException e) { + log.warn("缺少请求参数: {}", e.getParameterName()); + return RestBean.failure(RestCode.FAILURE, "缺少必要参数: " + e.getParameterName()); + } + + /** + * 4. 请求方法不支持 + * 场景:接口只写了 @PostMapping,前端却用 GET 请求访问 + */ + @ExceptionHandler(HttpRequestMethodNotSupportedException.class) + public RestBean handleMethodNotSupported(HttpRequestMethodNotSupportedException e) { + log.warn("请求方法不支持: method={}, supported={}", e.getMethod(), e.getSupportedHttpMethods()); + return RestBean.failure(RestCode.METHOD_NOT_SUPPORT, "不支持该请求方法: " + e.getMethod()); + } + + /** + * 5. 参数类型不匹配 (刚才你遇到的那个) + */ + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public RestBean handleTypeMismatch(MethodArgumentTypeMismatchException e) { + String msg = String.format("参数类型错误: 参数[%s] 需要 [%s]", e.getName(), e.getRequiredType().getSimpleName()); + log.warn("参数类型不匹配: {}", msg); + return RestBean.failure(RestCode.FAILURE, msg); + } + + // ========================================== + // 2. 业务逻辑与数据库错误 + // ========================================== + + /** + * 6. 自定义业务异常 (最常用!) + * 场景:你在 Service 层手动抛出 throw new BusinessException(403, "权限不足"); + */ + @ExceptionHandler(BusinessException.class) + public RestBean handleBusinessException(BusinessException e) { + // 业务异常通常是预期内的,记录 WARN 即可 + log.warn("业务异常: code={}, message={}", e.getCode(), e.getMessage()); + return RestBean.failure(e.getCode(), e.getMessage(),""); + } + + /** + * 7. 数据库唯一键冲突 + * 场景:注册时用户名已存在,插入数据库时触发 Unique Constraint + */ + @ExceptionHandler(DuplicateKeyException.class) + public RestBean handleDuplicateKeyException(DuplicateKeyException e) { + log.warn("数据库数据冲突: {}", e.getMessage()); + return RestBean.failure(RestCode.DATA_ALREADY_FOUND, "数据已存在,请勿重复操作"); + } + + // ========================================== + // 3. 致命系统错误 (500) + // 这类错误是 Bug,必须记录堆栈信息 (e),并报警 + // ========================================== + + /** + * 8. 空指针异常 (NullPointerException) + * 场景:代码里没做判空,a.b() 时 a 是 null + */ + @ExceptionHandler(NullPointerException.class) + public RestBean handleNPE(NullPointerException e) { + // 必须打印堆栈! + log.error("发生空指针异常: ", e); + return RestBean.failure(RestCode.SYSTEM_ERROR, "系统内部数据异常,请联系管理员"); + } /** * 处理权限拒绝异常 @@ -28,7 +150,7 @@ public class GlobalExceptionHandler { */ @ExceptionHandler(Exception.class) public RestBean handleException(Exception e) { - log.error("系统异常: ", e); + log.error("系统异常: ", e.getMessage()); return RestBean.failure(RestCode.SYSTEM_ERROR); } } diff --git a/backend/src/main/resources/application-dev.yaml b/backend/src/main/resources/application-dev.yaml index 829d501..9d7c22d 100644 --- a/backend/src/main/resources/application-dev.yaml +++ b/backend/src/main/resources/application-dev.yaml @@ -1,6 +1,6 @@ spring: datasource: - url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:news}?useSSL=false&serverTimezone=UTC&characterEncoding=UTF-8&allowPublicKeyRetrieval=true + url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME:news}?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8&allowPublicKeyRetrieval=true username: ${DB_USER:root} password: ${DB_PASS:root} driver-class-name: com.mysql.cj.jdbc.Driver diff --git a/client/src/App.vue b/client/src/App.vue index cb58276..022ff97 100644 --- a/client/src/App.vue +++ b/client/src/App.vue @@ -1,13 +1,26 @@ diff --git a/client/src/api/news.ts b/client/src/api/news.ts index bc0c61c..ad64c73 100644 --- a/client/src/api/news.ts +++ b/client/src/api/news.ts @@ -92,7 +92,8 @@ export const newsApi = { * @param size 每页大小 * @returns 分页新闻列表 */ - getLatest(page = 1, size = 20): Promise> { + getLatest(params: { page?: number; size?: number } = {}): Promise> { + const { page = 1, size = 20 } = params return http.get('/v1/news/latest', { params: { page, size } }) }, diff --git a/client/src/router/index.ts b/client/src/router/index.ts index 64ae039..3c484b9 100644 --- a/client/src/router/index.ts +++ b/client/src/router/index.ts @@ -11,6 +11,12 @@ const routes = [ component: () => import('@/views/auth/LoginView.vue'), meta: { requiresAuth: false } }, + { + path: '/register', + name: 'Register', + component: () => import('@/views/auth/RegisterView.vue'), + meta: { requiresAuth: false } + }, { path: '/home', name: 'Home', diff --git a/client/src/views/auth/LoginView.vue b/client/src/views/auth/LoginView.vue index 2200a18..c821894 100644 --- a/client/src/views/auth/LoginView.vue +++ b/client/src/views/auth/LoginView.vue @@ -58,11 +58,10 @@ async function handleSubmit() { } /** - * 跳转到注册页面(预留) + * 跳转到注册页面 */ function goToRegister() { - // TODO: 实现注册页面 - console.log('跳转到注册页面') + router.push('/register') } @@ -103,7 +102,7 @@ function goToRegister() { v-model="form.username" type="text" placeholder="请输入用户名" - autocomplete="username" + autocomplete="off" :disabled="loading" /> @@ -116,7 +115,7 @@ function goToRegister() { v-model="form.password" type="password" placeholder="请输入密码" - autocomplete="current-password" + autocomplete="off" :disabled="loading" /> @@ -218,18 +217,21 @@ function goToRegister() { .form-group input { padding: 0.75rem 1rem; - border: 1px solid hsl(var(--input)); + border: 1px solid #d1d5db; border-radius: var(--radius); background: hsl(var(--background)); color: hsl(var(--foreground)); font-size: 0.875rem; transition: border-color 0.2s, box-shadow 0.2s; + /* 增强默认边框可见性 */ + border-color: #d1d5db !important; } .form-group input:focus { outline: none; - border-color: hsl(var(--ring)); - box-shadow: 0 0 0 3px hsl(var(--ring) / 0.2); + /* 增强聚焦时的边框效果 */ + border-color: #3b82f6 !important; + box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.3); } .form-group input:disabled { diff --git a/client/src/views/auth/RegisterView.vue b/client/src/views/auth/RegisterView.vue new file mode 100644 index 0000000..0814660 --- /dev/null +++ b/client/src/views/auth/RegisterView.vue @@ -0,0 +1,315 @@ + + + + + \ No newline at end of file diff --git a/client/src/views/home/DashboardView.vue b/client/src/views/home/DashboardView.vue index ba67dfc..4571288 100644 --- a/client/src/views/home/DashboardView.vue +++ b/client/src/views/home/DashboardView.vue @@ -19,7 +19,7 @@ const { data: latestNews, loading: newsLoading, fetch: fetchLatestNews -} = usePagination(newsApi.getLatest.bind(newsApi), { size: 5 }) +} = usePagination(newsApi.getLatest.bind(newsApi), { page: 1, size: 5 }) // 分类统计(计算属性) const categoryStats = computed(() => {