当前位置: 首页 > news >正文

手机单页网站生成系统书店网站策划书

手机单页网站生成系统,书店网站策划书,小型企业网站开发价格,电商推广平台有哪些后端用户模块开发 制定参数交互约束 当前#xff0c;我们使用MybatisX工具快速生成的代码中#xff0c;包含了一个实体类#xff0c;这个类中包含我们数据表中的所有字段。 但因为有些字段#xff0c;是不应该返回到前端的#xff0c;比如用户密码#xff0c;或者前端传…后端用户模块开发 制定参数交互约束 当前我们使用MybatisX工具快速生成的代码中包含了一个实体类这个类中包含我们数据表中的所有字段。 但因为有些字段是不应该返回到前端的比如用户密码或者前端传递参数时有一些字段我们根本不需要比如登录时只需要账号密码其他字段用不上。所以在业务模块新建 model 目录专门存放用于前后端交换的数据模型并创建dto和vo目录 dto用于接收前端请求参数的类vo返回给前端的封装类 如用户功能的登录和注册可以针对需要传入的参数新建两个DTO UserLoginRequest接收用户登录时所需传入的请求参数 Data public class UserLoginRequest implements Serializable {private static final long serialVersionUID 3132234234234234234L;/*** 用户账号*/private String userAccount;/*** 用户密码*/private String userPassword; }UserRegisterRequest接收用户注册时所需传入的请求参数 Data public class UserRegisterRequest implements Serializable {private static final long serialVersionUID 3132234234234234234L;/*** 用户账号*/private String userAccount;/*** 用户密码*/private String userPassword;/*** 校验密码*/private String checkPassword; }而我们要返回给前端指定用户信息时只需要新建一个用户信息封装类将要传给前端哪些字段写到该类中 UserInfoVO Data public class UserInfoVO implements Serializable {/*** 用户表主键*/private Long userId;/*** 用户账号*/private String userAccount;/*** 用户邮件*/private String userEmail;/*** 用户头像*/private String userAvatar;private static final long serialVersionUID 1L; }完善用户信息表 用户信息表新增角色、简介、昵称等字段 # 用户信息表新增角色、简介、昵称等字段 ALTER TABLE user_infoADD COLUMN user_role varchar(256) NOT NULL DEFAULT user COMMENT 用户角色:USER/ADMIN,ADD COLUMN user_profile varchar(256) NULL DEFAULT COMMENT 用户简介,ADD COLUMN user_name varchar(256) NOT NULL DEFAULT 无名 COMMENT 用户昵称;# 创建基于用户名称的索引 CREATE INDEX idx_user_name ON user_info (user_name);此处新增字段后你可以将原来生成的文件删除重新用MybatisX再生成一次如果还没改过生成文件的代码是可以这么操作的。这里手动添加 修改domain/UserInfo /*** 用户信息表** TableName user_info*/ TableName(value user_info) Data public class UserInfo implements Serializable {/*** 用户表主键*/TableId(type IdType.AUTO)private Long userId;/*** 用户账号*/private String userAccount;/*** 用户密码*/private String userPassword;/*** 用户昵称*/private String userName;/*** 用户简介*/private String userProfile;/*** 用户角色USER/ADMIN*/private String userRole;/*** 用户邮件*/private String userEmail;/*** 用户头像*/private String userAvatar;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;/*** 是否删除*/private Integer isDelete;TableField(exist false)private static final long serialVersionUID 1L; }修改resources/mapper/UserInfoMapper.xml文件 ?xml version1.0 encodingUTF-8? !DOCTYPE mapperPUBLIC -//mybatis.org//DTD Mapper 3.0//ENhttp://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.cyfy.cyblogsbackend.business.mapper.UserInfoMapperresultMap idBaseResultMap typecom.cyfy.cyblogsbackend.business.domain.UserInfoid propertyuserId columnuser_id jdbcTypeBIGINT/result propertyuserAccount columnuser_account jdbcTypeVARCHAR/result propertyuserPassword columnuser_password jdbcTypeVARCHAR/result propertyuserName columnuser_name jdbcTypeVARCHAR/result propertyuserProfile columnuser_profile jdbcTypeVARCHAR/result propertyuserRole columnuser_role jdbcTypeVARCHAR/result propertyuserEmail columnuser_email jdbcTypeVARCHAR/result propertyuserAvatar columnuser_avatar jdbcTypeVARCHAR/result propertycreateTime columncreate_time jdbcTypeTIMESTAMP/result propertyupdateTime columnupdate_time jdbcTypeTIMESTAMP/result propertyisDelete columnis_delete jdbcTypeTINYINT//resultMapsql idBase_Column_Listuser_id,user_account,user_password,user_name,user_profile,user_role,user_email,user_avatar,create_time,update_time,is_delete/sql /mapper库表设计很难做到一次就设计好后续不用再修改可能随着功能的不断开发会不停更新增删改库表字段所以还是熟悉一下改变库表后需要改哪些文件。 登录注册接口实现 对应登录用户信息我们可以编写一个登录用户信息封装类用于返回脱敏后的当前登录用户信息 LoginUserVO Data public class LoginUserVO implements Serializable {/*** 用户表主键*/private Long userId;/*** 用户账号*/private String userAccount;/*** 用户昵称*/private String userName;/*** 用户简介*/private String userProfile;/*** 用户角色USER/ADMIN*/private String userRole;/*** 用户邮件*/private String userEmail;/*** 用户头像*/private String userAvatar;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;private static final long serialVersionUID 1L; }UserInfoService添加接口方法 public interface UserInfoService extends IServiceUserInfo {/*** 用户注册* param userRegisterRequest 用户注册请求参数* return 新注册用户id*/long userRegister(UserRegisterRequest userRegisterRequest);/*** 用户登录* param userLoginRequest 用户登录请求参数* param request* return 存有脱敏后的用户信息的token令牌*/String userLogin(UserLoginRequest userLoginRequest, HttpServletRequest request);/*** 用户信息脱敏处理* param userInfo* return*/LoginUserVO getLoginUserVO(UserInfo userInfo); }UserInfoServiceImpl接口方法实现 Service public class UserInfoServiceImpl extends ServiceImplUserInfoMapper, UserInfoimplements UserInfoService {Resourceprivate JwtUtil jwtUtil;/*** 用户注册** param userRegisterRequest 用户注册请求参数* return 新注册用户id*/Overridepublic long userRegister(UserRegisterRequest userRegisterRequest) {String userAccount userRegisterRequest.getUserAccount();String userPassword userRegisterRequest.getUserPassword();String checkPassword userRegisterRequest.getCheckPassword();// 校验if (StrUtil.hasBlank(userAccount, userPassword, checkPassword)){throw new BusinessException(ErrorCode.PARAMS_ERROR, 参数为空);}if (userAccount.length() 4){throw new BusinessException(ErrorCode.PARAMS_ERROR, 用户账号过短);}if (userAccount.length() 25){throw new BusinessException(ErrorCode.PARAMS_ERROR, 用户账号过长);}if (userPassword.length() 8){throw new BusinessException(ErrorCode.PARAMS_ERROR, 用户密码过短);}if (userPassword.length() 30){throw new BusinessException(ErrorCode.PARAMS_ERROR, 用户密码过长);}if (!userPassword.equals(checkPassword)){throw new BusinessException(ErrorCode.PARAMS_ERROR, 两次输入的密码不一致);}// 检查账号是否已被注册QueryWrapperUserInfo queryWrapper new QueryWrapper();queryWrapper.eq(user_account, userAccount);long count this.count(queryWrapper);if(count 0){throw new BusinessException(ErrorCode.PARAMS_ERROR,注册账号已存在);}// 密码加密String encryptPassword EncipherUtils.hashPsd(userPassword);// 插入数据UserInfo userInfo new UserInfo();userInfo.setUserAccount(userAccount);userInfo.setUserPassword(encryptPassword);userInfo.setUserName(临时名);userInfo.setUserRole(USER);boolean saveResult this.save(userInfo);if (!saveResult){throw new BusinessException(ErrorCode.PARAMS_ERROR,注册失败,数据库错误);}return userInfo.getUserId();}/*** 用户登录** param userLoginRequest 用户登录请求参数* param request* return 存有脱敏后的用户信息的token令牌*/Overridepublic String userLogin(UserLoginRequest userLoginRequest, HttpServletRequest request) {String userAccount userLoginRequest.getUserAccount();String userPassword userLoginRequest.getUserPassword();// 校验if (StrUtil.hasBlank(userAccount, userPassword)){throw new BusinessException(ErrorCode.PARAMS_ERROR, 参数为空);}if (userAccount.length() 4|| userPassword.length() 8|| userAccount.length() 25|| userPassword.length() 30){throw new BusinessException(ErrorCode.PARAMS_ERROR, 账号或密码错误);}// 查询用户是否存在QueryWrapperUserInfo queryWrapper new QueryWrapper();queryWrapper.eq(user_account, userAccount);UserInfo user this.getOne(queryWrapper);// 用户不存在if ( user null ){throw new BusinessException(ErrorCode.ACCOUNT_NOT_EXIST);}// 校验密码if (!EncipherUtils.checkPsd(userPassword, user.getUserPassword())){throw new BusinessException(ErrorCode.PASSWORD_ERROR);}// 记录用户的登录态request.getSession().setAttribute(user_login, user.getUserId());// 转换成封装类并存入LoginUserVO loginUserVO this.getLoginUserVO(user);return jwtUtil.createToken(loginUserVO);}/*** 用户信息脱敏处理** param userInfo* return*/Overridepublic LoginUserVO getLoginUserVO(UserInfo userInfo) {if (userInfo null){return null;}LoginUserVO loginUserVO new LoginUserVO();BeanUtils.copyProperties(userInfo, loginUserVO);return loginUserVO;} }UserController移除之前的测试方法编写登录注册接口 RestController RequestMapping(/user) public class UserController {Resourceprivate UserInfoService userInfoService;PostMapping(/login)public BaseResponseString userLogin(RequestBody UserLoginRequest userLoginRequest, HttpServletRequest request){ThrowUtils.throwIf(userLoginRequest null, ErrorCode.PARAMS_ERROR);String loginUserToken userInfoService.userLogin(userLoginRequest, request);return ResultUtils.success(loginUserToken);}PostMapping(/register)public BaseResponseLong userRegister(RequestBody UserRegisterRequest userRegisterRequest){ThrowUtils.throwIf(userRegisterRequest null, ErrorCode.PARAMS_ERROR);long result userInfoService.userRegister(userRegisterRequest);return ResultUtils.success(result);} }这里我们之前设置表的时候将用户邮件和头像设置成NOT NULL但我们没有设置值为了方便测试这里修改表属性。 # 修改用户邮件和用户头像字段为可空 ALTER TABLE user_infoMODIFY COLUMN user_email varchar(256) NULL COMMENT 用户邮件,MODIFY COLUMN user_avatar varchar(256) NULL COMMENT 用户头像;测试接口登录注册接口的输入参数受到约束不再先之前那样可以输入所有UserInfo字段 因为我们用户信息已经存到token令牌中并返回给前端后续前端需要登录用户数据只需在token中获取即可。 你也可以不用token而是直接将脱敏后的用户信息存到session中 获取当前登录用户 UserInfoService增加获取当前用户信息方法 /*** 获取当前登录用户* param request* return*/ LoginUserVO getCurrentLoginUser(HttpServletRequest request);UserInfoServiceImpl实现getCurrentLoginUser方法 /*** 获取当前登录用户** param request* return*/Overridepublic LoginUserVO getCurrentLoginUser( HttpServletRequest request) {// 从session中获取用户IDObject userIdObject request.getSession().getAttribute(user_login);Long userId (Long) userIdObject;// 如果当前连接的session中不存在用户idif (userId null){throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, 未登录);}// 获取前端请求头中的X-Token数据String token request.getHeader(X-Token);if (StrUtil.isBlank(token)){throw new BusinessException(ErrorCode.TOKEN_ERROR);}LoginUserVO currentUser;try{// 将token转换成对象currentUser jwtUtil.parseToken(token, LoginUserVO.class);}catch (ExpiredJwtException e) {throw new BusinessException(ErrorCode.TOKEN_ERROR, 令牌已过期请重新登录);} catch (MalformedJwtException e) {throw new BusinessException(ErrorCode.TOKEN_ERROR, 无效令牌请重新登录);} catch (Exception e) {throw new BusinessException(ErrorCode.TOKEN_ERROR);}// 判断当前session中存放的用户id与token中的用户id是否一致if (!currentUser.getUserId().equals(userId)){throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, 非法登录);}return currentUser;}ErrorCode状态码枚举类增加令牌相关枚举值 TOKEN_ERROR(40020, 无效令牌)UserController增加获取当前登录用户接口 GetMapping(/get/login) public BaseResponseLoginUserVO getLoginUser(HttpServletRequest request){LoginUserVO result userInfoService.getCurrentLoginUser(request);return ResultUtils.success(result); }测试 需要先登录获取令牌后在全局参数设置中添加X-Token再去调用获取接口 此时如果获取了token后重启服务器session会被清掉如果再想调用该接口只通过token去获得用户信息是行不通的 小优化 common 模块新建constant目录用于存放开发中用到的常量。 新建UserConstant用于记录用户相关常量 /*** 用户相关常量*/ public interface UserConstant {/*** 用户登录态键*/String USER_LOGIN_STATE user_login;/*** Token令牌存储键*/String TOKEN_KEY X-Token; }替换当前代码中用到登录态键和Token令牌存储键的地方改成使用常量代替避免后续使用该常量时编写错误 最终UserInfoServiceImpl代码 package com.cyfy.cyblogsbackend.business.service.impl; import java.util.Date;import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.cyfy.cyblogsbackend.business.domain.UserInfo; import com.cyfy.cyblogsbackend.business.model.dto.user.UserLoginRequest; import com.cyfy.cyblogsbackend.business.model.dto.user.UserRegisterRequest; import com.cyfy.cyblogsbackend.business.model.vo.LoginUserVO; import com.cyfy.cyblogsbackend.business.service.UserInfoService; import com.cyfy.cyblogsbackend.business.mapper.UserInfoMapper; import com.cyfy.cyblogsbackend.common.constant.UserConstant; import com.cyfy.cyblogsbackend.common.exception.BusinessException; import com.cyfy.cyblogsbackend.common.exception.ErrorCode; import com.cyfy.cyblogsbackend.common.tools.EncipherUtils; import com.cyfy.cyblogsbackend.framework.jwt.JwtUtil; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.MalformedJwtException; import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service;import javax.annotation.Resource; import javax.servlet.http.HttpServletRequest;/*** author cy* description 针对表【user_info(用户信息表)】的数据库操作Service实现* createDate 2025-02-21 21:46:22*/ Service public class UserInfoServiceImpl extends ServiceImplUserInfoMapper, UserInfoimplements UserInfoService {Resourceprivate JwtUtil jwtUtil;/*** 用户注册** param userRegisterRequest 用户注册请求参数* return 新注册用户id*/Overridepublic long userRegister(UserRegisterRequest userRegisterRequest) {String userAccount userRegisterRequest.getUserAccount();String userPassword userRegisterRequest.getUserPassword();String checkPassword userRegisterRequest.getCheckPassword();// 校验if (StrUtil.hasBlank(userAccount, userPassword, checkPassword)){throw new BusinessException(ErrorCode.PARAMS_ERROR, 参数为空);}if (userAccount.length() 4){throw new BusinessException(ErrorCode.PARAMS_ERROR, 用户账号过短);}if (userAccount.length() 25){throw new BusinessException(ErrorCode.PARAMS_ERROR, 用户账号过长);}if (userPassword.length() 8){throw new BusinessException(ErrorCode.PARAMS_ERROR, 用户密码过短);}if (userPassword.length() 30){throw new BusinessException(ErrorCode.PARAMS_ERROR, 用户密码过长);}if (!userPassword.equals(checkPassword)){throw new BusinessException(ErrorCode.PARAMS_ERROR, 两次输入的密码不一致);}// 检查账号是否已被注册QueryWrapperUserInfo queryWrapper new QueryWrapper();queryWrapper.eq(user_account, userAccount);long count this.count(queryWrapper);if(count 0){throw new BusinessException(ErrorCode.PARAMS_ERROR,注册账号已存在);}// 密码加密String encryptPassword EncipherUtils.hashPsd(userPassword);// 插入数据UserInfo userInfo new UserInfo();userInfo.setUserAccount(userAccount);userInfo.setUserPassword(encryptPassword);userInfo.setUserName(临时名);userInfo.setUserRole(USER);boolean saveResult this.save(userInfo);if (!saveResult){throw new BusinessException(ErrorCode.PARAMS_ERROR,注册失败,数据库错误);}return userInfo.getUserId();}/*** 用户登录** param userLoginRequest 用户登录请求参数* param request* return 存有脱敏后的用户信息的token令牌*/Overridepublic String userLogin(UserLoginRequest userLoginRequest, HttpServletRequest request) {String userAccount userLoginRequest.getUserAccount();String userPassword userLoginRequest.getUserPassword();// 校验if (StrUtil.hasBlank(userAccount, userPassword)){throw new BusinessException(ErrorCode.PARAMS_ERROR, 参数为空);}if (userAccount.length() 4|| userPassword.length() 8|| userAccount.length() 25|| userPassword.length() 30){throw new BusinessException(ErrorCode.PARAMS_ERROR, 账号或密码错误);}// 查询用户是否存在QueryWrapperUserInfo queryWrapper new QueryWrapper();queryWrapper.eq(user_account, userAccount);UserInfo user this.getOne(queryWrapper);// 用户不存在if ( user null ){throw new BusinessException(ErrorCode.ACCOUNT_NOT_EXIST);}// 校验密码if (!EncipherUtils.checkPsd(userPassword, user.getUserPassword())){throw new BusinessException(ErrorCode.PASSWORD_ERROR);}// 记录用户的登录态request.getSession().setAttribute(UserConstant.USER_LOGIN_STATE, user.getUserId());// 转换成封装类并转换为令牌LoginUserVO loginUserVO this.getLoginUserVO(user);return jwtUtil.createToken(loginUserVO);}/*** 获取当前登录用户** param request* return*/Overridepublic LoginUserVO getCurrentLoginUser( HttpServletRequest request) {// 从session中获取用户id用于校验令牌合法性Object userIdObject request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);Long userId (Long) userIdObject;// 如果当前连接的session中存储的用户idif (userId null){throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, 未登录);}// 获取前端请求头中的X-Token数据String token request.getHeader(UserConstant.TOKEN_KEY);if (StrUtil.isBlank(token)){throw new BusinessException(ErrorCode.TOKEN_ERROR);}LoginUserVO currentUser;try{// 将token转换成对象currentUser jwtUtil.parseToken(token, LoginUserVO.class);}catch (ExpiredJwtException e) {throw new BusinessException(ErrorCode.TOKEN_ERROR, 令牌已过期请重新登录);} catch (MalformedJwtException e) {throw new BusinessException(ErrorCode.TOKEN_ERROR, 无效令牌请重新登录);} catch (Exception e) {throw new BusinessException(ErrorCode.TOKEN_ERROR);}// 判断当前用户id是否与token中的用户id一致if (!currentUser.getUserId().equals(userId)){throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR, 非法登录);}return currentUser;}/*** 用户信息脱敏处理** param userInfo* return*/Overridepublic LoginUserVO getLoginUserVO(UserInfo userInfo) {if (userInfo null){return null;}LoginUserVO loginUserVO new LoginUserVO();BeanUtils.copyProperties(userInfo, loginUserVO);return loginUserVO;} }用户注销接口实现 UserInfoService增加用户注销登录方法 /*** 用户注销* param request* return*/ boolean userLogout(HttpServletRequest request);UserInfoServiceImpl实现用户注销登录方法 /*** 用户注销** param request* return*/Overridepublic boolean userLogout(HttpServletRequest request) {// 先判断用户是否登录Object userIdObject request.getSession().getAttribute(UserConstant.USER_LOGIN_STATE);if (userIdObject null){throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);}// 移除登录态request.getSession().removeAttribute(UserConstant.USER_LOGIN_STATE);return true;}UserController增加注销接口 PostMapping(/logout) public BaseResponseBoolean userLogout(HttpServletRequest request){ThrowUtils.throwIf(request null, ErrorCode.PARAMS_ERROR);boolean result userInfoService.userLogout(request);return ResultUtils.success(result); }测试用户登录后注销前可正常获取当前登录用户信息注销后无法获取当前登录用户信息 AOP切面编程实现权限控制 common模块下导入依赖 !-- spring aop -- dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-aop/artifactId /dependency启动类添加EnableAspectJAutoProxy(exposeProxy true)注解 SpringBootApplication EnableAsync EnableAspectJAutoProxy(exposeProxy true) public class MainApplication {public static void main(String[] args) {SpringApplication.run(MainApplication.class, args);} }common模块新建annotation和enums用于存放自定义注解和通用枚举类 AuthCheck权限校验注解 Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface AuthCheck {/*** 必须有某个角色*/String mustRole() default ; }UserRoleEnum用户角色枚举类 Getter public enum UserRoleEnum {USER(用户,USER),ADMIN(管理员,ADMIN);private final String text;private final String value;UserRoleEnum(String text, String value) {this.text text;this.value value;}/**** 根据value获取枚举* param value 枚举值的value* return 枚举值*/public static UserRoleEnum getEnumByValue(String value) {if (ObjUtil.isEmpty(value)) {return null;}for (UserRoleEnum anEnum : UserRoleEnum.values()) {if (anEnum.value.equals(value)) {return anEnum;}}return null;} }UserConstant增加用户角色相关常量 public interface UserConstant {// 登录用户相关常量/*** 用户登录态键*/String USER_LOGIN_STATE user_login;/*** Token令牌存储键*/String TOKEN_KEY X-Token;// 权限角色相关常量/*** 默认角色*/String DEFAULT_ROLE USER;/*** 管理员角色*/String ADMIN_ROLE ADMIN; }admin模块新建aop目录编写切面编程代码 AuthInterceptor当访问接口有AuthCheck注解时进行权限判断 Aspect Component Slf4j public class AuthInterceptor {Resourceprivate UserInfoService userInfoService;/*** 执行拦截** param joinPoint 切入点* param authCheck 权限校验注解*/Around(annotation(authCheck))public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {// 获取当前访问接口所需要的权限String mustRole authCheck.mustRole();UserRoleEnum mustRoleEnum UserRoleEnum.getEnumByValue(mustRole);String methodName joinPoint.getSignature().getName();String className joinPoint.getSignature().getDeclaringTypeName();log.info(当前访问接口:{}.{},需要权限{}, className,methodName,mustRole);// 不需要权限放行if (mustRoleEnum null) {return joinPoint.proceed();}// 需要权限判断当前用户是否具有权限RequestAttributes requestAttributes RequestContextHolder.currentRequestAttributes();HttpServletRequest request ((ServletRequestAttributes) requestAttributes).getRequest();// 获取当前登录用户LoginUserVO currentLoginUser userInfoService.getCurrentLoginUser(request);// 获取当前用户具有的权限UserRoleEnum userRoleEnum UserRoleEnum.getEnumByValue(currentLoginUser.getUserRole());log.info(当前用户权限{}, userRoleEnum);// 没有权限拒绝if (userRoleEnum null) {throw new BusinessException(ErrorCode.NO_AUTH_ERROR);}// 要求必须有管理员权限但用户没有管理员权限拒绝if (UserRoleEnum.ADMIN.equals(mustRoleEnum) !UserRoleEnum.ADMIN.equals(userRoleEnum)) {throw new BusinessException(ErrorCode.NO_AUTH_ERROR);}// 通过权限校验放行return joinPoint.proceed();} }测试 TestController编写权限测试接口 RestController RequestMapping(/test) public class TestController {GetMapping(/test)public BaseResponseString test() {return ResultUtils.success(所有人可访问);}GetMapping(/test1)AuthCheck(mustRole UserConstant.DEFAULT_ROLE)public BaseResponseString test1() {return ResultUtils.success(普通用户访问);}GetMapping(/test2)AuthCheck(mustRole UserConstant.ADMIN_ROLE)public BaseResponseString test2() {return ResultUtils.success(管理员用户访问);} }未登录账号 普通用户 管理员用户 这里需要注意重新登录需要重新设置新的token令牌并点击接口旁的重置按钮 这里其实有个小问题就是修改用户权限后重新登录不修改token的话用户信息还是之前的也就是重新登录的用户可以使用未过期的token。但怎么想也是无关痛痒的时毕竟你本身就能正常登录。 当然如果出现权限变更的情况还是可能会出现点问题毕竟我们后续都会用getCurrentLoginUser方法获取用户权限而不是重新从数据库中获取解决方法也很简单一是把登录键改成随机UID并在登录用户封装类上新增登录键字段并赋相同的值这样只要判断二者的值是否相同即可。二是将其放到Redis等缓存中重登时覆盖缓存中对应的token即可如果使用旧的token进行操作缓存中没有则抛出非法操作这也意味着不允许同时登同一个账号 小优化 修改UserInfouserId字段由普通自增id改为雪花idisDelete字段增加逻辑删除注解 TableName(value user_info) Data public class UserInfo implements Serializable {/*** 用户表主键*/TableId(type IdType.ASSIGN_ID)private Long userId;/*** 用户账号*/private String userAccount;/*** 用户密码*/private String userPassword;/*** 用户昵称*/private String userName;/*** 用户简介*/private String userProfile;/*** 用户角色USER/ADMIN*/private String userRole;/*** 用户邮件*/private String userEmail;/*** 用户头像*/private String userAvatar;/*** 创建时间*/private Date createTime;/*** 更新时间*/private Date updateTime;/*** 是否删除*/TableLogicprivate Integer isDelete;TableField(exist false)private static final long serialVersionUID 1L; }效果新注册账号id为雪花id 修改服务器配置设置访问接口需要加一层/api # 开发环境配置 server:# 服务器的HTTP端口默认为 8080port: 8081address: 0.0.0.0servlet:context-path: /api前端同步修改 openapi.config.js import { generateService } from umijs/openapigenerateService({requestLibPath: import request from /request,schemaPath: http://localhost:8080/api/v2/api-docs,serversPath: ./src, })schemaPath为host basePath 分组Url才对 先前使用代理转发是错误的因为请求路径不包含/api 前端用户登录模块开发 当前前端只是简单搭建了基础的页面框架和实现了前后端交互方法并没有什么实际的页面。 因为后端已经实现了简单的登录注册接口所以前端先完成登录注册功能 使用 openapi 更新接口调用方法 小优化 前面我把导航栏放在 components 中但components目录应该存放通用的、能复用的组件而非这种只用一次的样式布局组件所以将 GlobalHeader.vue 移植 layouts/header下 然后为了更好管理整合的组件这里 components 目录新增组件类型目录然后新建个 index.ts 做统一导出管理。比如登录弹窗为对话框组件那么在存放在指定目录下然后在 index.ts 文件中添加如下代码 // 登录弹窗 export { default as LoginModal } from /components/modal/LoginModal.vue后续需要引用该组件时使用以下格式引入 import { LoginModal } from /components目录结构如下 实现登录弹窗 登录窗口按理说只要未登录就应该能在网站任何位置打开注不是做成页面而是弹窗的形式 回到导航组件 GlobalHeader.vue 中因为要做登录弹窗这里希望是点击【登录】按钮弹出登录弹窗所以在该组件中添加弹窗代码 在 Ant Design 官网中找到合适的对话框组件粘贴至 GlobalHeader 中 代码 templatediv classglobalHeagera-row :wrapfalse......a-col flex120pxdiv classuser-login-statusdiv v-ifloginUserStore.loginUser.userId{{ loginUserStore.loginUser.userName ?? 无名 }}/divdiv v-elsea-button typeprimary clickshowModal 登录/a-buttona-modal v-model:openopen width1000px titleBasic Modal okhandleOkpSome contents.../ppSome contents.../ppSome contents.../p/a-modal/div/div/a-col/a-row/div /templatescript langts setup import { h, ref } from vue; ...... // 登录弹窗 const open refboolean(false);const showModal () {open.value true; };const handleOk (e: MouseEvent) {console.log(e);open.value false; }; ...... /script 运行效果 可以在官网 下面看到组件所有属性 这里不希望对话框有按钮所以添加:footernull并移除okhandleOk templatediv classglobalHeagera-row :wrapfalse......a-col flex120pxdiv classuser-login-statusdiv v-ifloginUserStore.loginUser.userId{{ loginUserStore.loginUser.userName ?? 无名 }}/divdiv v-elsea-button typeprimary clickshowModal 登录/a-buttona-modalv-model:openopenwidth1000pxtitleBasic Modal:footernullpSome contents.../ppSome contents.../ppSome contents.../p/a-modal/div/div/a-col/a-row/div /templatescript langts setup import { h, ref } from vue; ...... // 登录弹窗 const open refboolean(false);const showModal () {open.value true; };const handleOk (e: MouseEvent) {console.log(e);open.value false; }; ...... /script 运行效果 实现登录表单 有了弹窗后就可以开始写我们的登录表单同样的到官网找喜欢的表单组件 代码 templatediv classglobalHeagera-row :wrapfalse......a-col flex120pxdiv classuser-login-statusdiv v-ifloginUserStore.loginUser.userId{{ loginUserStore.loginUser.userName ?? 无名 }}/divdiv v-elsea-button typeprimary clickshowModal登录/a-buttona-modal v-model:openopen width1000px titleBasic Modal :footernullpSome contents.../pa-form :modelformState namenormal_login classlogin-form finishonFinishfinishFailedonFinishFaileda-form-item label账号 nameusername:rules[{ required: true, message: 请输入登录账号! }]a-input v-model:valueformState.usernametemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-item label密码 namepassword:rules[{ required: true, message: 请输入登录密码! }]a-input-password v-model:valueformState.passwordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-itema-form-item nameremember no-stylea-checkbox v-model:checkedformState.remember记住密码/a-checkbox/a-form-itema classlogin-form-forgot href忘记密码/a/a-form-itema-form-itema-button :disableddisabled typeprimary html-typesubmit classlogin-form-button登录/a-buttonOra href去注册!/apSome contents.../p/a-modal/div/div/a-col/a-row/div /templatescript langts setup import { h, ref,reactive, computed } from vue; import { HomeOutlined,UserOutlined, LockOutlined } from ant-design/icons-vue; ......// 登录表单interface FormState {username: string;password: string;remember: boolean; } const formState reactiveFormState({username: ,password: ,remember: true, }); const onFinish (values: any) {console.log(Success:, values); };const onFinishFailed (errorInfo: any) {console.log(Failed:, errorInfo); }; const disabled computed(() {return !(formState.username formState.password); }); ...... /scriptstyle scoped ...... /* 登录表单 */ #components-form-demo-normal-login .login-form {max-width: 300px; } #components-form-demo-normal-login .login-form-forgot {float: right; } #components-form-demo-normal-login .login-form-button {width: 100%; }/style 运行效果 优化 当前弹窗宽度设置为1000px导致输入框很长我们可以去掉弹窗宽度 让其暂时好看一些 a-modal v-model:openopen titleBasic Modal :footernull...... /a-modal这里使用后端请求参数的格式约束参数类型而非自己定义 templatediv classglobalHeager......!-- 登录弹窗 --a-modal v-model:openopen titleBasic Modal :footernullpSome contents.../pa-form :modelloginFormRefnamenormal_loginclasslogin-formfinishonFinishfinishFailedonFinishFaileda-form-item label账号 nameuserAccount:rulesloginFormRules.userAccounta-input v-model:valueloginFormRef.userAccounttemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-item label密码 nameuserPassword:rulesloginFormRules.userPassworda-input-password v-model:valueloginFormRef.userPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-itema-form-item nameremember no-stylea-checkbox v-model:checkedloginFormRef.remember记住密码/a-checkbox/a-form-itema classlogin-form-forgot href忘记密码/a/a-form-itema-form-itema-button typeprimary html-typesubmit classlogin-form-button登录/a-buttonOra href去注册!/a/a-form-item/a-formpSome contents.../p/a-modal....../div /template script langts setup ...... // 登录表单 /*** 上传到后端的表单数据*/ const loginForm reactiveAPI.UserLoginRequest({userAccount: ,userPassword: , });/*** 表单字段*/ const loginFormRef reactive({...loginForm,remember:false })/*** 定义登录表单校验规则*/ const loginFormRules {userAccount: [{ required: true, message: 请输入账号, trigger: blur },{ min: 4, max: 25, message: 长度在 4 到 25 个字符, trigger: blur },],userPassword: [{ required: true, message: 请输入密码, trigger: blur },{ min: 8, max:30, message: 长度在 8 到 30 个字符,}] }/*** 表单校验通过时触发事件*/ const onFinish (values: any) {console.log(Success:, values); }; /*** 表单校验不通过失败触发事件*/ const onFinishFailed (errorInfo: any) {console.log(Failed:, errorInfo); };...... /script 提交表单成功或失败时都能获取输入框中的参数 登录按钮加载状态 发送请求时我们可以让登录按钮进入加载状态告知用户正在处理登录请求 a-button :loadingloginLoading typeprimary html-typesubmit classlogin-form-button登录 /a-button/*** 表单校验通过时触发事件*/ const onFinish (values: any) {loginLoading.value true;setTimeout(() {console.log(登录成功)}, 3000)console.log(Success:, values);loginLoading.value false; };该效果可在官网 按钮组件中找到复制想要效果即可 运行效果 表单提交 找到接口对应的方法 因为我们返回的是 token 令牌需要将其存到浏览器中这里存到localStorage中 src目录下新建utils目录用于存放工具类。编写auth.ts 提供操作Token的方法 const TokenKey cyfyblogkeyvalue // 获取本地存储的token export function getToken() {return localStorage.getItem(TokenKey) } // 将token存放到localStorage export function setToken(token: string) {return localStorage.setItem(TokenKey, token) } // 移除本地存储的token export function removeToken() {return localStorage.removeItem(TokenKey) }实现登录逻辑onFinish 方法 import { userLoginUsingPost } from /api/userController import { setToken } from /utils/auth /*** 表单校验通过时触发事件*/ const onFinish async (values: any) {loginLoading.value true;const res await userLoginUsingPost(values)// 登录成功if (res.data.code 0 res.data.data) {// 将token保存到cookie中setToken(res.data.data)// 登录成功更新登录用户信息await loginUserStore.fetchLoginUser()message.success(登录成功)// 关闭登录弹窗open.value false}else {message.error(登录失败: res.data.message)}loginLoading.value false; };修改 stores/useLoginUserStore.ts 文件实现获取用户数据方法 import { getLoginUserUsingGet } from /api/userController import { defineStore } from pinia import { ref } from vueexport const useLoginUserStore defineStore(loginUser, () {// 登录用户的初始值const loginUser refAPI.LoginUserVO({userName: 未登录,})async function fetchLoginUser() {// 从服务器获取用户信息const res await getLoginUserUsingGet()if (res.data.code 0 res.data.data) {loginUser.value res.data.data}}// 设置登录用户function setLoginUser(newLoginUser: any) {loginUser.value newLoginUser}return { loginUser, setLoginUser, fetchLoginUser } })修改request.ts文件发送请求时携带token // 全局配置拦截器 myAxios.interceptors.request.use((config) {// 在发送请求之前做些什么// 获取tokenconst token getToken()if (token) {// 将token添加到请求头中config.headers[X-Token] token}return config},(error) {// 对请求错误做些什么return Promise.reject(error)}, )运行效果 如果后端无法获取请求头中的“X-Token”可以尝试重启前后端项目 实现注册表单 这里注册和登录共有一个弹窗去官网 找个好看的标签页 复制后将登录表单代码放到标签中 div v-elsea-button typeprimary clickshowModal登录/a-button!-- 登录弹窗 --a-modal v-model:openopen titleBasic Modal :footernulla-tabs v-model:activeKeyactiveKey typecarda-tab-pane keylogin_tabs tab登录a-form :modelloginFormRefnamenormal_loginclasslogin-formfinishonFinishfinishFailedonFinishFaileda-form-item label账号 nameuserAccount:rulesloginFormRules.userAccounta-input v-model:valueloginFormRef.userAccounttemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-item label密码 nameuserPassword:rulesloginFormRules.userPassworda-input-password v-model:valueloginFormRef.userPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-itema-form-item nameremember no-stylea-checkbox v-model:checkedloginFormRef.remember记住密码/a-checkbox/a-form-itema classlogin-form-forgot href忘记密码/a/a-form-itema-form-itema-button :loadingloginLoading typeprimary html-typesubmit classlogin-form-button登录/a-button/a-form-item/a-form/a-tab-panea-tab-pane keyregister_tabs tab注册Content of Tab Pane 2/a-tab-pane/a-tabs/a-modal /div运行效果 注如果处于登录状态可以去控制台 - Application - Local Storage 中删除token令牌 注册表单的实现 与登录差不多就是找合适的表单组件然后修改这里因为注册只比登录多个确认密码所以直接复用登录表单 !-- 注册表单 -- a-tab-pane keyregister_tabs tab注册a-form :modelregisterFormRef namenormal_register classregister-formfinishonRegisterFinish finishFailedonRegisterFinishFaileda-form-item label登录账号 nameuserAccount :rulesregisterFormRules.userAccounta-input v-model:valueregisterFormRef.userAccounttemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-item label登录密码 nameuserPassword :rulesregisterFormRules.userPassworda-input-password v-model:valueregisterFormRef.userPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-item label确认密码 namecheckPassword :rulesregisterFormRules.checkPassworda-input-password v-model:valueregisterFormRef.checkPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-itema-button :loadingregisterLoading typeprimary html-typesubmitclassregister-form-button注册/a-button/a-form-item/a-form /a-tab-pane// 注册表单 /*** 上传到后端的表单数据*/ const registerFormRef reactiveAPI.UserRegisterRequest({userAccount: ,userPassword: ,checkPassword: });/*** 注册按钮载入状态*/ const registerLoading refboolean(false); /*** 校验两次密码是否一致*/const validateConfirmPassword (rule: any, value: string) {if (value value ! registerFormRef.userPassword) {return Promise.reject(两次密码不一致);}return Promise.resolve(); };/*** 定义注册表单校验规则*/ const registerFormRules {userAccount: [{ required: true, message: 请输入账号, trigger: blur },{ min: 4, max: 25, message: 长度在 4 到 25 个字符, trigger: blur },],userPassword: [{ required: true, message: 登录密码不能为空, trigger: blur },{ min: 8, max: 30, message: 长度在 8 到 30 个字符, }],checkPassword: [{ required: true, message: 确认密码不能为空, trigger: blur },{ validator: validateConfirmPassword, trigger: blur },], }/*** 表单校验通过时触发事件*/ const onRegisterFinish async (values: any) {registerLoading.value true;// 注册方法const res await userRegisterUsingPost(values)// 注册成功if (res.data.code 0 res.data.data) {// 注册成功message.success(注册成功)// 切换到登录标签你也可以直接调用登录方法直接帮用户登录activeKey.value login_tabs}else {message.error(注册失败: res.data.message)}registerLoading.value false; }; /*** 表单校验不通过失败触发事件*/ const onRegisterFinishFailed (errorInfo: any) {console.log(Failed:, errorInfo); };运行效果 后台新增数据 小优化 目前的登录注册表单都写在一个文件里仅仅只是两个小表单就使得GlobalHeader.vue 文件有300多行代码后续我们可能还会追加一些内容以及优化登录注册的代码 正如我前面说的我不喜欢把全部代码都在一个文件中写完虽然可以但还是将代码各个不同模块拆分成组件更好一些 新建 layouts/header/component 目录用于存放由导航栏GlobalHeader分离的组件文件 GlobalHeader.vue templatediv classglobalHeagera-row :wrapfalsea-col flex200pxRouterLink to/div classtitle-barimg classlogo src/assets/logo.jpeg altlogo /div classtitle我的博客/div/div/RouterLink/a-cola-col flexautoa-menu v-model:selectedKeyscurrent modehorizontal :itemsitems clickdoMenuClick //a-cola-col flex120pxdiv classuser-login-statusdiv v-ifloginUserStore.loginUser.userId{{ loginUserStore.loginUser.userName ?? 无名 }}/divdiv v-elsea-button typeprimary clickshowModal登录/a-button!-- 登录弹窗 --a-modal v-model:openopen titleBasic Modal :footernull!-- 登录表单 --a-tabs v-model:activeKeyactiveKey typecarda-tab-pane keylogin_tabs tab登录LoginForm loginSuccesshandleLoginSuccess //a-tab-pane!-- 注册表单 --a-tab-pane keyregister_tabs tab注册RegisterForm registerSuccesshandleRegisterSuccess //a-tab-pane/a-tabs/a-modal/div/div/a-col/a-row/div /templatescript langts setup import { h, ref } from vue; import { HomeOutlined } from ant-design/icons-vue; import { type MenuProps } from ant-design-vue; import { useRouter } from vue-router; import { useLoginUserStore } from /stores/useLoginUserStore import LoginForm from ./component/LoginForm.vue; import RegisterForm from ./component/RegisterForm.vue; const loginUserStore useLoginUserStore() const router useRouter();// 登录注册标签 const activeKey ref(login_tabs);// 登录弹窗 const open refboolean(false);const showModal () {open.value true; };// 菜单点击事件跳转指定路由 const doMenuClick ({ key }: { key: string }) {router.push({path: key}); }// 当前选中菜单 const current refstring[]([/]); // 监听路由变化更新当前选中菜单 router.afterEach((to) {current.value [to.path]; });// 菜单项 const items refMenuProps[items]([{key: /,icon: () h(HomeOutlined),label: 主页,title: 我的博客,},{key: /about,label: 关于,title: 关于,}, ]);// 处理子组件事件 const handleRegisterSuccess (key: string) {// 切换到登录标签activeKey.value key }; const handleLoginSuccess (bool: boolean) {// 关闭弹窗open.value bool }; /scriptstyle scoped .title-bar {display: flex;align-items: center; }.title {color: black;font-size: 18px;margin-left: 16px; }.logo {height: 48px; } /stylecomponent/LoginForm.vue templatediv classlogin-forma-form :modelloginFormRef namenormal_login classlogin-form finishonFinishfinishFailedonFinishFaileda-form-item label账号 nameuserAccount :rulesloginFormRules.userAccounta-input v-model:valueloginFormRef.userAccounttemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-item label密码 nameuserPassword :rulesloginFormRules.userPassworda-input-password v-model:valueloginFormRef.userPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-itema-form-item nameremember no-stylea-checkbox v-model:checkedloginFormRef.remember记住密码/a-checkbox/a-form-itema classlogin-form-forgot href忘记密码/a/a-form-itema-form-itema-button :loadingloginLoading typeprimary html-typesubmit classlogin-form-button登录/a-button/a-form-item/a-form/div /templatescript setup langts import { ref, reactive } from vue; import { UserOutlined, LockOutlined } from ant-design/icons-vue; import { message } from ant-design-vue; import { useRouter } from vue-router; import { useLoginUserStore } from /stores/useLoginUserStore import { userLoginUsingPost } from /api/userController import { setToken } from /utils/auth const loginUserStore useLoginUserStore() // 登录表单 /*** 上传到后端的表单数据*/ const loginForm reactiveAPI.UserLoginRequest({userAccount: ,userPassword: , });/*** 表单字段*/ const loginFormRef reactive({...loginForm,remember: false })/*** 登录按钮载入状态*/ const loginLoading refboolean(false);/*** 定义登录表单校验规则*/ const loginFormRules {userAccount: [{ required: true, message: 请输入账号, trigger: blur },{ min: 4, max: 25, message: 长度在 4 到 25 个字符, trigger: blur },],userPassword: [{ required: true, message: 请输入密码, trigger: blur },{ min: 8, max: 30, message: 长度在 8 到 30 个字符, }] } const emit defineEmits([loginSuccess]); /*** 表单校验通过时触发事件*/ const onFinish async (values: any) {loginLoading.value true;const res await userLoginUsingPost(values)// 登录成功if (res.data.code 0 res.data.data) {// 将token保存到cookie中setToken(res.data.data)// 登录成功更新登录用户信息await loginUserStore.fetchLoginUser()message.success(登录成功)// 关闭登录弹窗// 调用父组件的方法emit(loginSuccess, false);}else {message.error(登录失败: res.data.message)}loginLoading.value false; }; /*** 表单校验不通过失败触发事件*/ const onFinishFailed (errorInfo: any) {console.log(Failed:, errorInfo); }; /scriptstyle scoped /* 登录表单 */ #components-form-demo-normal-login .login-form {max-width: 300px; }#components-form-demo-normal-login .login-form-forgot {float: right; }#components-form-demo-normal-login .login-form-button {width: 100%; } /stylecomponent/RegisterForm.vue templatediv classregister-forma-form :modelregisterFormRef namenormal_register classregister-formfinishonRegisterFinish finishFailedonRegisterFinishFaileda-form-item label登录账号 nameuserAccount :rulesregisterFormRules.userAccounta-input v-model:valueregisterFormRef.userAccounttemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-item label登录密码 nameuserPassword :rulesregisterFormRules.userPassworda-input-password v-model:valueregisterFormRef.userPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-item label确认密码 namecheckPassword :rulesregisterFormRules.checkPassworda-input-password v-model:valueregisterFormRef.checkPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-itema-button :loadingregisterLoading typeprimary html-typesubmitclassregister-form-button注册/a-button/a-form-item/a-form/div /templatescript setup langts import {ref, reactive } from vue; import { UserOutlined, LockOutlined } from ant-design/icons-vue; import { message } from ant-design-vue; import { userRegisterUsingPost } from /api/userController// 注册表单 /*** 上传到后端的表单数据*/const registerFormRef reactiveAPI.UserRegisterRequest({userAccount: ,userPassword: ,checkPassword: });/*** 注册按钮载入状态*/ const registerLoading refboolean(false); /*** 校验两次密码是否一致*/const validateConfirmPassword (rule: any, value: string) {if (value value ! registerFormRef.userPassword) {return Promise.reject(两次密码不一致);}return Promise.resolve(); };/*** 定义注册表单校验规则*/ const registerFormRules {userAccount: [{ required: true, message: 请输入账号, trigger: blur },{ min: 4, max: 25, message: 长度在 4 到 25 个字符, trigger: blur },],userPassword: [{ required: true, message: 登录密码不能为空, trigger: blur },{ min: 8, max: 30, message: 长度在 8 到 30 个字符, }],checkPassword: [{ required: true, message: 确认密码不能为空, trigger: blur },{ validator: validateConfirmPassword, trigger: blur },], } const emit defineEmits([registerSuccess]); /*** 表单校验通过时触发事件*/ const onRegisterFinish async (values: any) {registerLoading.value true;// 注册方法const res await userRegisterUsingPost(values)// 注册成功if (res.data.code 0 res.data.data) {// 注册成功message.success(注册成功)// 切换到登录标签你也可以直接调用登录方法直接帮用户登录// 调用父组件的方法emit(registerSuccess, login_tabs);}else {message.error(注册失败: res.data.message)}registerLoading.value false; }; /*** 表单校验不通过失败触发事件*/ const onRegisterFinishFailed (errorInfo: any) {console.log(Failed:, errorInfo); }; /scriptstyle scoped /* 注册表单 */ #components-form-demo-normal-register .register-form {max-width: 300px; }#components-form-demo-normal-register .register-form-forgot {float: left; }#components-form-demo-normal-register .register-form-button {width: 100%; } /style项目结构 此刻运行项目与之前效果差不多但后续要修改代码时我们可以直接找对应组件文件去修改即可 父子组件间的交互 此处使用 emit 事件来调用父组件的方法 // 定义一个名为 registerSuccess 的事件。 const emit defineEmits([registerSuccess]); // 触发 registerSuccess 事件并传递数据。 emit(registerSuccess, login_tabs);在父组件通过事件名方式接收并触发指定方法 !-- 监听 registerSuccess 事件并在事件触发时调用 handleRegisterSuccess 方法。 -- RegisterForm registerSuccesshandleRegisterSuccess /如果是父组件要传值给子组件那么直接在子组件中使用:参数名参数值 PictureList :dataListdataList :loadingloading/子组件接收数据 // 定义接入数据的类型 interface Props{dataList?: API.PictureVO[],loading?: boolean }// 接收父组件传入的数据 const props withDefaults(definePropsProps(),{dataList:() [],loading: false, })小优化 上面子组件通过emit派发事件的方法调用父组件中的方法是由 AI 生成的应该是最普遍的调用方法。这种方法并没有什么不妥只是需要我们去定义指定的事件名如果项目中要用到emit的地方多了就要想怎么规避使用到相同的事件名。 考虑到这点我自己去各大论坛网站中找了一个我认为不错的调用方法代码如下 // 组件传值在类型定义中声明父组件要传入的方法 /*** 组件属性类型定义*/interface Props{registerSuccess: (v: string) void; } /*** 组件初始值*/ const props withDefaults(definePropsProps(), {registerSuccess: (v: string) {console.log(v);} })父组件只需要在调用子组件时添加传入方法即可 LoginForm :loginSuccesshandleLoginSuccess /这种方法的好处在于我们不需要去考虑怎么定义派发的事件名这对于通用的、需要复用多次的组件来说是非常好的一件事 完整代码 LoginForm.vue templatediv classlogin-forma-form :modelloginFormRef namenormal_login classlogin-form finishonFinishfinishFailedonFinishFaileda-form-item label账号 nameuserAccount :rulesloginFormRules.userAccounta-input v-model:valueloginFormRef.userAccounttemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-item label密码 nameuserPassword :rulesloginFormRules.userPassworda-input-password v-model:valueloginFormRef.userPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-itema-form-item nameremember no-stylea-checkbox v-model:checkedloginFormRef.remember记住密码/a-checkbox/a-form-itema classlogin-form-forgot href忘记密码/a/a-form-itema-form-itema-button :loadingloginLoading typeprimary html-typesubmit classlogin-form-button登录/a-button/a-form-item/a-form/div /templatescript setup langts import { ref, reactive } from vue; import { UserOutlined, LockOutlined } from ant-design/icons-vue; import { message } from ant-design-vue; import { useRouter } from vue-router; import { useLoginUserStore } from /stores/useLoginUserStore import { userLoginUsingPost } from /api/userController import { setToken } from /utils/auth const loginUserStore useLoginUserStore() // 组件传值 /*** 组件属性类型*/interface Props{loginSuccess: (v: boolean) void; } /*** 组件初始值*/ const props withDefaults(definePropsProps(), {loginSuccess: (v: boolean) {} })// 登录表单 /*** 上传到后端的表单数据*/ const loginForm reactiveAPI.UserLoginRequest({userAccount: ,userPassword: , });/*** 表单字段*/ const loginFormRef reactive({...loginForm,remember: false })/*** 登录按钮载入状态*/ const loginLoading refboolean(false);/*** 定义登录表单校验规则*/ const loginFormRules {userAccount: [{ required: true, message: 请输入账号, trigger: blur },{ min: 4, max: 25, message: 长度在 4 到 25 个字符, trigger: blur },],userPassword: [{ required: true, message: 请输入密码, trigger: blur },{ min: 8, max: 30, message: 长度在 8 到 30 个字符, }] } // const emit defineEmits([loginSuccess]); /*** 表单校验通过时触发事件*/ const onFinish async (values: any) {loginLoading.value true;const res await userLoginUsingPost(values)// 登录成功if (res.data.code 0 res.data.data) {// 将token保存到cookie中setToken(res.data.data)// 登录成功更新登录用户信息await loginUserStore.fetchLoginUser()message.success(登录成功)// 关闭登录弹窗// 调用父组件的方法// emit(loginSuccess, false);props.loginSuccess(false)}else {message.error(登录失败: res.data.message)}loginLoading.value false; }; /*** 表单校验不通过失败触发事件*/ const onFinishFailed (errorInfo: any) {console.log(Failed:, errorInfo); }; /scriptstyle scoped /* 登录表单 */ #components-form-demo-normal-login .login-form {max-width: 300px; }#components-form-demo-normal-login .login-form-forgot {float: right; }#components-form-demo-normal-login .login-form-button {width: 100%; } /styleRegisterForm.vue templatediv classregister-forma-form :modelregisterFormRef namenormal_register classregister-formfinishonRegisterFinish finishFailedonRegisterFinishFaileda-form-item label登录账号 nameuserAccount :rulesregisterFormRules.userAccounta-input v-model:valueregisterFormRef.userAccounttemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-item label登录密码 nameuserPassword :rulesregisterFormRules.userPassworda-input-password v-model:valueregisterFormRef.userPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-item label确认密码 namecheckPassword :rulesregisterFormRules.checkPassworda-input-password v-model:valueregisterFormRef.checkPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-itema-button :loadingregisterLoading typeprimary html-typesubmitclassregister-form-button注册/a-button/a-form-item/a-form/div /templatescript setup langts import {ref, reactive } from vue; import { UserOutlined, LockOutlined } from ant-design/icons-vue; import { message } from ant-design-vue; import { userRegisterUsingPost } from /api/userController// 组件传值 /*** 组件属性类型*/interface Props{registerSuccess: (v: string) void; } /*** 组件初始值*/ const props withDefaults(definePropsProps(), {registerSuccess: (v: string) {} })// 注册表单 /*** 上传到后端的表单数据*/const registerFormRef reactiveAPI.UserRegisterRequest({userAccount: ,userPassword: ,checkPassword: });/*** 注册按钮载入状态*/ const registerLoading refboolean(false); /*** 校验两次密码是否一致*/const validateConfirmPassword (rule: any, value: string) {if (value value ! registerFormRef.userPassword) {return Promise.reject(两次密码不一致);}return Promise.resolve(); };/*** 定义注册表单校验规则*/ const registerFormRules {userAccount: [{ required: true, message: 请输入账号, trigger: blur },{ min: 4, max: 25, message: 长度在 4 到 25 个字符, trigger: blur },],userPassword: [{ required: true, message: 登录密码不能为空, trigger: blur },{ min: 8, max: 30, message: 长度在 8 到 30 个字符, }],checkPassword: [{ required: true, message: 确认密码不能为空, trigger: blur },{ validator: validateConfirmPassword, trigger: blur },], } const emit defineEmits([registerSuccess]); /*** 表单校验通过时触发事件*/ const onRegisterFinish async (values: any) {registerLoading.value true;// 注册方法const res await userRegisterUsingPost(values)// 注册成功if (res.data.code 0 res.data.data) {// 注册成功message.success(注册成功)// 切换到登录标签你也可以直接调用登录方法直接帮用户登录// 调用父组件的方法// emit(registerSuccess, login_tabs);props.registerSuccess(login_tabs);}else {message.error(注册失败: res.data.message)}registerLoading.value false; }; /*** 表单校验不通过失败触发事件*/ const onRegisterFinishFailed (errorInfo: any) {console.log(Failed:, errorInfo); }; /scriptstyle scoped /* 注册表单 */ #components-form-demo-normal-register .register-form {max-width: 300px; }#components-form-demo-normal-register .register-form-forgot {float: left; }#components-form-demo-normal-register .register-form-button {width: 100%; } /styleGlobalHeader.vue templatediv classglobalHeagera-row :wrapfalsea-col flex200pxRouterLink to/div classtitle-barimg classlogo src/assets/logo.jpeg altlogo /div classtitle我的博客/div/div/RouterLink/a-cola-col flexautoa-menu v-model:selectedKeyscurrent modehorizontal :itemsitems clickdoMenuClick //a-cola-col flex120pxdiv classuser-login-statusdiv v-ifloginUserStore.loginUser.userId{{ loginUserStore.loginUser.userName ?? 无名 }}/divdiv v-elsea-button typeprimary clickshowModal登录/a-button!-- 登录弹窗 --a-modal v-model:openopen titleBasic Modal :footernull!-- 登录表单 --a-tabs v-model:activeKeyactiveKey typecarda-tab-pane keylogin_tabs tab登录LoginForm :loginSuccesshandleLoginSuccess //a-tab-pane!-- 注册表单 --a-tab-pane keyregister_tabs tab注册RegisterForm :registerSuccesshandleRegisterSuccess //a-tab-pane/a-tabs/a-modal/div/div/a-col/a-row/div /templatescript langts setup import { h, ref } from vue; import { HomeOutlined } from ant-design/icons-vue; import { type MenuProps } from ant-design-vue; import { useRouter } from vue-router; import { useLoginUserStore } from /stores/useLoginUserStore import LoginForm from ./component/LoginForm.vue; import RegisterForm from ./component/RegisterForm.vue; const loginUserStore useLoginUserStore() const router useRouter();// 登录注册标签 const activeKey ref(login_tabs);// 登录弹窗 const open refboolean(false);const showModal () {open.value true; };// 菜单点击事件跳转指定路由 const doMenuClick ({ key }: { key: string }) {router.push({path: key}); }// 当前选中菜单 const current refstring[]([/]); // 监听路由变化更新当前选中菜单 router.afterEach((to) {current.value [to.path]; });// 菜单项 const items refMenuProps[items]([{key: /,icon: () h(HomeOutlined),label: 主页,title: 我的博客,},{key: /about,label: 关于,title: 关于,}, ]);// 处理子组件事件 const handleRegisterSuccess (key: string) {// 切换到登录标签activeKey.value key }; const handleLoginSuccess (bool: boolean) {// 关闭弹窗open.value bool }; /scriptstyle scoped .title-bar {display: flex;align-items: center; }.title {color: black;font-size: 18px;margin-left: 16px; }.logo {height: 48px; } /style实现下拉菜单功能 老规矩去Ant Design官网中找合适的组件就以CSDN为例我们希望用户注销功能是在鼠标移动到用户头像时展开的下拉列表中的 所以可以到下拉菜单组件中找一个自己喜欢的下拉菜单样式 修改名称位置 div v-ifloginUserStore.loginUser.userIda-dropdown placementbottoma-button{{ loginUserStore.loginUser.userName ?? 无名 }}/a-buttontemplate #overlaya-menua-menu-itema target_blank rel href个人信息/a/a-menu-itema-menu-itema target_blank rel href占位/a/a-menu-itema-menu-itema target_blank rel href注销/a/a-menu-item/a-menu/template/a-dropdown/div运行效果 这里需要了解的时用户名那里虽然官网给的示例代码是button样式但你可以改成任何样式比如改成头像。 a-dropdown placementbottom!-- 改成头像 --a-avatar styleborder: 1px solid black;sizelarge srchttps://xsgames.co/randomusers/avatar.php?gpixelkey1 /template #overlaya-menua-menu-itema target_blank rel href个人信息/a/a-menu-itema-menu-itema target_blank rel href占位/a/a-menu-itema-menu-itema target_blank rel href注销/a/a-menu-item/a-menu/template /a-dropdown修改效果 同样的下面的菜单选项部分你可以当做是一个特殊的弹窗弹窗内容你可随意编写。如你把登录注册弹窗的内容写在里面 a-dropdown placementbottoma-avatar styleborder: 1px solid black; sizelarge srchttps://xsgames.co/randomusers/avatar.php?gpixelkey1 /template #overlayLoginForm :loginSuccesshandleLoginSuccess //template /a-dropdown那么效果就会是这样 即你不必局限于下拉菜单内容是menu菜单样式。它可以是任意内容。 参考CSDN的下拉菜单我们也可以在菜单项上方加入用户信息 templatetemplate #overlaya-menu classuser-dropdown-menudiv classuser-dropdown-menu-info用户名{{ loginUserStore.loginUser.userName }}a-menu-divider /其他信息/diva-menu-divider /a-menu-itema target_blan rel href个人信息/a/a-menu-itema-menu-itema target_blank rel href占位/a/a-menu-itema-menu-divider /a-menu-itema target_blank rel href注销/a/a-menu-item/a-menu/template /template ...... style scoped....../** 下拉菜单样式 */.user-dropdown-menu {width: 200px;}.user-dropdown-menu .user-dropdown-menu-info {padding-top: 20px;text-align: center;height: 100px;} /style修改效果 老规矩进行组件化管理。我们将用户菜单这一模块代码单独抽离出来做成登录用户模块组件 LoginUserModule.vue templatediv classuser-login-modulediv v-ifloginUserStore.loginUser.userIda-dropdown placementbottoma-avatar classant-dropdown-link styleborder: 1px solid black; sizelargesrchttps://xsgames.co/randomusers/avatar.php?gpixelkey1 /template #overlaya-menu classuser-dropdown-menudiv classuser-dropdown-menu-info用户名{{ loginUserStore.loginUser.userName }}a-menu-divider /其他信息/diva-menu-divider /a-menu-itema target_blan rel href个人信息/a/a-menu-itema-menu-itema target_blank rel href占位/a/a-menu-itema-menu-divider /a-menu-itema target_blank rel href注销/a/a-menu-item/a-menu/template/a-dropdown/divdiv v-elsea-button typeprimary clickshowModal登录/a-button!-- 登录弹窗 --a-modal v-model:openopen titleBasic Modal :footernull!-- 登录表单 --a-tabs v-model:activeKeyactiveKey typecarda-tab-pane keylogin_tabs tab登录LoginForm :loginSuccesshandleLoginSuccess //a-tab-pane!-- 注册表单 --a-tab-pane keyregister_tabs tab注册RegisterForm :registerSuccesshandleRegisterSuccess //a-tab-pane/a-tabs/a-modal/div/div /templatescript setup langts import { ref } from vue; import LoginForm from ./LoginForm.vue; import RegisterForm from ./RegisterForm.vue; import { useLoginUserStore } from /stores/useLoginUserStoreconst loginUserStore useLoginUserStore()// 登录注册标签 const activeKey ref(login_tabs);// 登录弹窗 const open refboolean(false);const showModal () {open.value true; };// 处理子组件事件 const handleRegisterSuccess (key: string) {// 切换到登录标签activeKey.value key }; const handleLoginSuccess (bool: boolean) {// 关闭弹窗open.value bool };/scriptstyle scoped /** 下拉菜单样式 */ .user-dropdown-menu {width: 200px; }.user-dropdown-menu .user-dropdown-menu-info {padding-top: 20px;text-align: center;height: 100px; } /styleGlobalHeader.vue templatediv classglobalHeagera-row :wrapfalsea-col flex200pxRouterLink to/div classtitle-barimg classlogo src/assets/logo.jpeg altlogo /div classtitle我的博客/div/div/RouterLink/a-cola-col flexautoa-menu v-model:selectedKeyscurrent modehorizontal :itemsitems clickdoMenuClick //a-cola-col flex120pxLoginUserModule //a-col/a-row/div /templatescript langts setup import { h, ref } from vue; import { HomeOutlined } from ant-design/icons-vue; import { type MenuProps } from ant-design-vue; import { useRouter } from vue-router; import LoginUserModule from ./component/LoginUserModule.vue; const router useRouter();// 菜单点击事件跳转指定路由 const doMenuClick ({ key }: { key: string }) {router.push({path: key}); }// 当前选中菜单 const current refstring[]([/]); // 监听路由变化更新当前选中菜单 router.afterEach((to) {current.value [to.path]; });// 菜单项 const items refMenuProps[items]([{key: /,icon: () h(HomeOutlined),label: 主页,title: 我的博客,},{key: /about,label: 关于,title: 关于,}, ]); /scriptstyle scoped .title-bar {display: flex;align-items: center; } .title {color: black;font-size: 18px;margin-left: 16px; }.logo {height: 48px; } /style后面我们要编写登录用户的下拉菜单相关的代码只需在LoginUserModule.vue编写即可 实现用户注销功能 到 api/UserController.ts 文件中找到注销接口的方法 接着在登录用户模块编写注销登录的方法调用接口的同时我们还要清除存在localStorage中的token令牌以及重置全局变量loginUserStore的内容 给注销选项增加点击事件loginUserLogout a-menu-item clickloginUserLogout退出登录 /a-menu-item实现退出登录逻辑 // 登录用户注销 const loginUserLogout async () {const res await userLogoutUsingPost()if (res.data.code 0) {// 删除tokenremoveToken()// 重置用户信息loginUserStore.setLoginUser({userName: 未登录,})message.success(退出登录成功)// TODO 考虑用户可能在需要特定权限的页面退出登录后续可能需要做重定向// await router.push(/)}else {message.error(退出登录失败: res.data.message)} }运行效果 页面美化 弹窗样式 修改 LoginUserModule.vue 文件 !-- 取消标题居中显示 -- a-modal v-model:openopen :footernull width480px centered!-- 表单增加样式 -- a-tabs v-model:activeKeyactiveKey typecard classform-modal-tabsstyle scoped .......form-modal-tabs {margin-top: 40px;padding: 0px 24px; } /style style /** 页签样式写在组件样式中不生效需写在全局中 */ /** 设置页签宽度与窗口相同 */ .form-modal-tabs .ant-tabs-nav-list, .form-modal-tabs .ant-tabs-tab {width: 100%; } /style修改 LoginForm.vue 文件 !-- 按钮与父样式同宽 -- a-button:loadingloginLoadingtypeprimaryhtml-typesubmitblockclasslogin-form-button登录 /a-buttonstyle scoped /** 清除无用样式设置按钮高度 **/ .login-form-button {height:38px; } /style修改 RegisterForm.vue 文件 a-form-itema-button :loadingregisterLoading typeprimary html-typesubmit block classregister-form-button注册/a-button /a-form-itemstyle scoped /* 注册表单 */ .register-form-button {height: 38px; } /style实现记住密码功能 回到登录表单组件我们表单中有“记住密码”选项但实际这个功能逻辑并没有实现所以需要我们在修改代码增加实现代码。 实现方法有很多但无非就是存储到浏览器中像我们存储Token令牌一样但这里我选择存到cookie中并用jsencrypt加密密码 安装依赖 npm i js-cookie // cookie操作工具 npm install crypto-js -S // 加密/解密工具新建 utils/encrypt.ts 文件编写加密解密工具类 import CryptoJS from crypto-js// 自定义密钥和偏移量 const KEY CryptoJS.enc.Utf8.parse(aaDJL2d9DfhLZO0z) // 密钥 const IV CryptoJS.enc.Utf8.parse(412ADDSSFA342442) // 偏移量/** AES加密 */ export function Encrypt(word: string) {let srcs CryptoJS.enc.Utf8.parse(word)var encrypted CryptoJS.AES.encrypt(srcs, KEY, {iv: IV,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.ZeroPadding,})return CryptoJS.enc.Base64.stringify(encrypted.ciphertext) }/** AES 解密 */ export function Decrypt(word: string) {let base64 CryptoJS.enc.Base64.parse(word)let src CryptoJS.enc.Base64.stringify(base64)var decrypt CryptoJS.AES.decrypt(src, KEY, {iv: IV,mode: CryptoJS.mode.CBC,padding: CryptoJS.pad.ZeroPadding,})var decryptedStr decrypt.toString(CryptoJS.enc.Utf8)return decryptedStr.toString() }编辑LoginForm.vue 文件实现记住密码功能 引入 Cookie 和 加密工具 import Cookies from js-cookie; import { Encrypt, Decrypt } from /utils/encrypt修改提交表单操作提交成功时添加用户账号密码 /*** 表单校验通过时触发事件*/ const onFinish async (values: any) {loginLoading.value true;// 将登录账号存储到cookie中,有效期 30 天Cookies.set(loginAccount, values.userAccount, { expires: 30 })const res await userLoginUsingPost(values)// 登录成功if (res.data.code 0 res.data.data) {// 将token保存到cookie中setToken(res.data.data)// 如果用户勾选了记住密码则将加密后的密码保存到cookie中if (values.remember) {// 对密码进行加密const encryptPassword Encrypt(JSON.stringify(values.userPassword))// 将登录账号存储到cookie中,有效期 30 天Cookies.set(loginPassword, encryptPassword, { expires: 30 })} else {// 删除cookieCookies.remove(loginPassword)}// 登录成功更新登录用户信息await loginUserStore.fetchLoginUser()message.success(登录成功)// 关闭登录弹窗// 调用父组件的方法props.loginSuccess(false)}else {message.error(登录失败: res.data.message)}loginLoading.value false; };增加从Cookie中加载用户数据的方法 /*** 从Cookie中加载用户信息*/ const resetLoginForm async () {// 读取账号、密码const userAccount Cookies.get(loginAccount)const userPassword Cookies.get(loginPassword)if (userAccount) {loginFormRef.userAccount userAccount}if (userPassword) {// 如果存在密码则说明用户勾选了记住密码则自动勾选记住密码loginFormRef.userPassword userPasswordloginFormRef.remember true} }/*** 打开表单时加载一次*/ onMounted(() {resetLoginForm() });这里加密后的密码长度可能会变长所以需要自定义校验规则放行未操作的加密密码 /*** 密码长度校验规则绕过加密密码*/const validatePasswordLength (rule: any, value: string) {const userPassword Cookies.get(loginPassword)// 如果当前登录密码和缓存密码一致则通过校验if (userPassword value userPassword) {return Promise.resolve();}// 否则判断长度是否符合要求if ( value.length 8 || value.length 30 ) {return Promise.reject(长度在 8 到 30 个字符);}return Promise.resolve(); };/*** 定义登录表单校验规则*/ const loginFormRules {userAccount: [{ required: true, message: 请输入账号, trigger: blur },{ min: 4, max: 25, message: 长度在 4 到 25 个字符, trigger: blur },],userPassword: [{ required: true, message: 请输入密码, trigger: blur },{ validator: validatePasswordLength, trigger: blur } // 自定义密码长度校验// { min: 8, max: 30, message: 长度在 8 到 30 个字符, }] }因为通过读取cookie获取的密码是加密后的密码不能直接给后端所以需要再次修改提交表单的逻辑 /*** 表单校验通过时触发事件*/ const onFinish async (values: any) {loginLoading.value true;// 将登录账号存储到cookie中,有效期 30 天Cookies.set(loginAccount, values.userAccount, { expires: 30 })const oldPassword Cookies.get(loginPassword)// 如果当前输入框中的密码与cookie中的一致就做解密处理if (oldPassword oldPassword values.userPassword){// 解析加密后的数据values.userPassword await JSON.parse(Decrypt(values.userPassword));}const res await userLoginUsingPost(values)if (res.data.code 0 res.data.data) {setToken(res.data.data)if (values.remember) {const encryptPassword Encrypt(JSON.stringify(values.userPassword))Cookies.set(loginPassword, encryptPassword, { expires: 30 })} else {Cookies.remove(loginPassword)}await loginUserStore.fetchLoginUser()message.success(登录成功)props.loginSuccess(false)}else {message.error(登录失败: res.data.message)}loginLoading.value false; };LoginForm.vue完整代码 templatediv classlogin-forma-form:modelloginFormRefnamenormal_loginfinishonFinisha-form-item label账号 nameuserAccount :rulesloginFormRules.userAccounta-input v-model:valueloginFormRef.userAccounttemplate #prefixUserOutlined classsite-form-item-icon //template/a-input/a-form-itema-form-item label密码 nameuserPassword :rulesloginFormRules.userPassworda-input-password v-model:valueloginFormRef.userPasswordtemplate #prefixLockOutlined classsite-form-item-icon //template/a-input-password/a-form-itema-form-itema-form-item nameremember no-stylea-checkbox v-model:checkedloginFormRef.remember记住密码/a-checkbox/a-form-itema classlogin-form-forgot href忘记密码/a/a-form-itema-form-item a-button:loadingloginLoadingtypeprimaryhtml-typesubmitblockclasslogin-form-button登录/a-button/a-form-item/a-form/div /templatescript setup langts import { ref, reactive } from vue; import { UserOutlined, LockOutlined } from ant-design/icons-vue; import { message } from ant-design-vue; import { useLoginUserStore } from /stores/useLoginUserStore import { userLoginUsingPost } from /api/userController import { setToken } from /utils/auth import Cookies from js-cookie; import { Encrypt, Decrypt } from /utils/encrypt import { onMounted } from vue; const loginUserStore useLoginUserStore()// 组件传值 /*** 组件属性类型*/interface Props{loginSuccess: (v: boolean) void; } /*** 组件初始值*/ const props withDefaults(definePropsProps(), {loginSuccess: (v: boolean) {}, })// 登录表单 /*** 上传到后端的表单数据*/ const loginForm reactiveAPI.UserLoginRequest({userAccount: ,userPassword: , });/*** 表单字段*/ const loginFormRef reactive({...loginForm,remember: false })/*** 登录按钮载入状态*/ const loginLoading refboolean(false);/*** 密码长度校验规则绕过加密密码*/const validatePasswordLength (rule: any, value: string) {const userPassword Cookies.get(loginPassword)// 如果当前登录密码和缓存密码一致则通过校验if (userPassword value userPassword) {return Promise.resolve();}// 否则判断长度是否符合要求if ( value.length 8 || value.length 30 ) {return Promise.reject(长度在 8 到 30 个字符);}return Promise.resolve(); };/*** 定义登录表单校验规则*/ const loginFormRules {userAccount: [{ required: true, message: 请输入账号, trigger: blur },{ min: 4, max: 25, message: 长度在 4 到 25 个字符, trigger: blur },],userPassword: [{ required: true, message: 请输入密码, trigger: blur },{ validator: validatePasswordLength, trigger: blur } // 自定义密码长度校验// { min: 8, max: 30, message: 长度在 8 到 30 个字符, }] }/*** 从Cookie中加载用户信息*/ const resetLoginForm async () {// 读取账号、密码const userAccount Cookies.get(loginAccount)const userPassword Cookies.get(loginPassword)if (userAccount) {loginFormRef.userAccount userAccount}if (userPassword) {// 如果存在密码则说明用户勾选了记住密码则自动勾选记住密码loginFormRef.userPassword userPasswordloginFormRef.remember true} } /*** 打开表单时加载一次*/ onMounted(() {resetLoginForm() });/*** 表单校验通过时触发事件*/ const onFinish async (values: any) {loginLoading.value true;// 将登录账号存储到cookie中,有效期 30 天Cookies.set(loginAccount, values.userAccount, { expires: 30 })const oldPassword Cookies.get(loginPassword)// 如果当前输入框中的密码与cookie中的一致就做解密处理if (oldPassword oldPassword values.userPassword){// 解析加密后的数据values.userPassword await JSON.parse(Decrypt(values.userPassword));}const res await userLoginUsingPost(values)// 登录成功if (res.data.code 0 res.data.data) {// 将token保存到cookie中setToken(res.data.data)// 如果用户勾选了记住密码则将加密后的密码保存到cookie中if (values.remember) {// 对密码进行加密const encryptPassword Encrypt(JSON.stringify(values.userPassword))// 将登录账号存储到cookie中,有效期 30 天Cookies.set(loginPassword, encryptPassword, { expires: 30 })} else {// 删除cookieCookies.remove(loginPassword)}// 登录成功更新登录用户信息await loginUserStore.fetchLoginUser()message.success(登录成功)// 关闭登录弹窗// 调用父组件的方法props.loginSuccess(false)}else {message.error(登录失败: res.data.message)}loginLoading.value false; }; /script style scoped .login-form-button {height:38px; } /style运行效果 小优化 当前关闭弹窗后表单信息还会存在比如校验状态之类的且不会重新加载一次用户数据读取。解决方法也很简单在弹窗组件中加destroyOnClose属性即可 !-- 登录弹窗 -- a-modalv-model:openopen:footernullwidth480pxdestroyOnClosecenteredPS 这里我把登录和注册写在同一弹窗中但看现在的效果其实还行 但这主要是注册表单仅仅只有3个字段而已实际注册还有用户名、用户头像、联系电话等内容需要填写当字段躲起来的时候左右切换就会出现弹窗高度不同变化的情况。 所以后面打算再花点时间将注册做成一个页面用户点击前往注册时关闭窗口并跳转至注册页
http://www.hkea.cn/news/14472401/

相关文章:

  • 山西做网站的公司织梦怎么建设论坛网站
  • 国外做电商网站有哪些教育平台小程序
  • 什么网站可以用视频做背景网站建设竞价托管什么意思
  • 建设个人博客网站网站怎么做自适应
  • 智慧团建官方网站登录入口网站建设费计入无形资产
  • 蛋糕网站建设金乡网站建设公司
  • 网站建设公司价位创新设计
  • 那些网站可以上传自己做的视频百度一下首页版
  • 做慕墙上什么网站好找事做抖音代运营ppt
  • 兼职做页面的网站m版网站开发
  • 人力资源外包服务包括哪些宁波网络seo哪家专业
  • 在线销售型的网站河南龙王建设集团网站
  • 顺义区网站建设北京icp网站备案
  • 腾讯域名注册网站如何招聘软件网站开发人员
  • 什么是成品网站南宁优化网站收费
  • 哪个网站买域名好研磨材料 东莞网站建设
  • 网站开发与维护专业网页制作平台flash
  • 产品展示网站 源码长春市大学生网站建设
  • 用6数字域名做网站的是wordpress 新建导航
  • 织梦网站安装视频太原百度seo网站建设
  • 中企动力网站案例有什么功能
  • 安卓做网站做简单的企业网站需要学哪些
  • 佛山做网站制作公司为什么有网网站打不开怎么回事啊
  • 网站漏洞怎么修复网站改版中 模板
  • 瓷砖网站建设2022百度seo优化工具
  • 网站建设的前后台代码移动的网络网站建设
  • 做美图+网站有哪些app开发制作网站平台
  • 营销型网站效果不好如何做好电商网站平面设计
  • 做网站如何链接邮箱简述建设网站的一般过程
  • 网站标题的设置方法五金配件店 东莞网站建设