一、结果截图(结果截图只是展现功能的一小部分,只要您把前后端项目运行起来探讨,方能体会该项目中的精髓,该项目是本人JavaEE期末实验作业,结合Javaweb课程所开发)
1.登录页面:
2.注册页面:
3.首页:
4.审核页面:
5.添加申报页面:
6.删除和批量删除
7.管理员基本信息:
8.管理员更换用户头像页面:
9.管理员更改密码页面:
10.账号注销页面:
二、代码实验(后端(springboot3+mybatisplus+redis+toke,注意:必须开启redis服务,必须登录用户才能查看首页,账号123456,密码123456)
1.创建数据库以及添加数据。
/*
Navicat MySQL Data Transfer
Source Server : 我的期望的
Source Server Type : MySQL
Source Server Version : 80031
Source Host : localhost:3306
Source Schema : projectdb
Target Server Type : MySQL
Target Server Version : 80031
File Encoding : 65001
Date: 20/06/2024 15:23:02
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for projectinf
-- ----------------------------
DROP TABLE IF EXISTS `projectinf`;
CREATE TABLE `projectinf` (
`id` int NOT NULL AUTO_INCREMENT COMMENT '项目编号',
`projectName` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL COMMENT '项目名称',
`startDate` date NOT NULL COMMENT '开始时间',
`endDate` date NOT NULL,
`status` int NOT NULL COMMENT '0-已申报\r\n1-审核中\r\n2-已审核',
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 18 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of projectinf
-- ----------------------------
INSERT INTO `projectinf` VALUES (1, '北京社会科学基金2011年度申报', '2024-06-15', '2024-06-15', 0);
INSERT INTO `projectinf` VALUES (2, '国家自然科学基金2011年度申报', '2024-06-14', '2024-06-14', 2);
INSERT INTO `projectinf` VALUES (3, '国家社会科学基金2011年度申报', '2024-06-11', '2024-06-14', 1);
INSERT INTO `projectinf` VALUES (20, '北京社会科学基金2018年度申报', '2024-06-03', '2024-06-20', 2);
INSERT INTO `projectinf` VALUES (19, '北京社会科学基金2015年度申报', '2024-06-03', '2024-06-10', 1);
INSERT INTO `projectinf` VALUES (18, '北京社会科学基金2012年度申报', '2024-06-02', '2024-06-21', 0);
INSERT INTO `projectinf` VALUES (21, '北京社会科学基金2021年度申报', '2024-06-10', '2024-06-26', 2);
INSERT INTO `projectinf` VALUES (22, '南京社会科学基金2011年度申报', '2024-06-10', '2024-06-20', 0);
INSERT INTO `projectinf` VALUES (23, '南京社会科学基金2021年度申报', '2024-06-04', '2024-06-20', 1);
INSERT INTO `projectinf` VALUES (24, '江苏社会科学基金2011年度申报', '2024-06-04', '2024-06-19', 2);
INSERT INTO `projectinf` VALUES (25, '南京自然科学基金2011年度申报', '2024-06-02', '2024-06-20', 1);
INSERT INTO `projectinf` VALUES (26, '南京自然科学基金2015年度申报', '2024-06-10', '2024-06-12', 1);
INSERT INTO `projectinf` VALUES (27, '广东自然科学基金2011年度申报', '2024-06-11', '2024-06-19', 0);
INSERT INTO `projectinf` VALUES (28, '海南自然科学基金2011年度申报', '2024-06-17', '2024-06-26', 1);
INSERT INTO `projectinf` VALUES (29, '海南自然科学基金2021年度申报', '2024-06-11', '2024-06-12', 1);
INSERT INTO `projectinf` VALUES (30, '海南自然科学基金2012年度申报', '2024-06-11', '2024-06-12', 1);
INSERT INTO `projectinf` VALUES (31, '海南自然科学基金2024年度申报', '2024-06-11', '2024-06-19', 2);
INSERT INTO `projectinf` VALUES (32, '广东自然科学基金2024年度申报', '2024-06-11', '2024-06-19', 1);
INSERT INTO `projectinf` VALUES (33, '新疆自然科学基金2024年度申报', '2024-06-10', '2024-06-19', 1);
INSERT INTO `projectinf` VALUES (34, '黑河自然科学基金2024年度申报', '2024-06-10', '2024-06-19', 2);
INSERT INTO `projectinf` VALUES (35, '海南自然科学基金2026年度申报', '2024-06-11', '2024-06-27', 0);
INSERT INTO `projectinf` VALUES (36, '上海社会科学基金2024年度申报', '2024-06-10', '2024-06-19', 1);
INSERT INTO `projectinf` VALUES (37, '上海社会科学基金2021年度申报', '2024-06-11', '2024-06-11', 0);
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int NOT NULL AUTO_INCREMENT,
`username` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`password` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NOT NULL,
`userpic` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`name` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`email` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`phone` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`address` varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_general_ci NULL DEFAULT NULL,
`isadmin` bit(1) NOT NULL DEFAULT b'0',
`isvalidate` bit(1) NOT NULL DEFAULT b'0',
PRIMARY KEY (`id`, `username`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 57 CHARACTER SET = utf8mb3 COLLATE = utf8mb3_general_ci ROW_FORMAT = DYNAMIC;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (35, '123', '202cb962ac59075b964b07152d234b70', 'https://big-eventa.oss-cn-beijing.aliyuncs.com/0696295f-a304-4a04-a3dd-de76da78dfd6.webp', '张三', '234234@232111', '12432467899', '广东省佛山市', b'0', b'0');
INSERT INTO `user` VALUES (52, '2342334', '0fd6f5a9f28ce32f8228594e7a2fc917', 'https://big-eventa.oss-cn-beijing.aliyuncs.com/31013a65-6e20-4f84-97f8-f02b0c7a0fcd.jpg', '', '', '', '', b'0', b'0');
INSERT INTO `user` VALUES (56, '123456', 'e10adc3949ba59abbe56e057f20f883e', 'https://big-eventa.oss-cn-beijing.aliyuncs.com/0696295f-a304-4a04-a3dd-de76da78dfd6.webp', '张三', '234234@232111', '12432467899', '广东省佛山市', b'0', b'0');
SET FOREIGN_KEY_CHECKS = 1;
2.创建springboot,项目名为springboot3_mybatisplus_redis_JWT。
3.导入pom.xml文件的依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.5</version>
</dependency>
4.在项目中创建所需的包
5.在resoures包下创建application.properties和application.yml。
(1)application.propertie配置:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/projectdb
spring.datasource.username=root
spring.datasource.password=123456
server.port=9095
#mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#mybatis.configuration.map-underscore-to-camel-case=true
spring.web.resources.static-locations=classpath:/
(2)application.yml:
spring:
mvc:
servlet:
load-on-startup: 1
spring:
redis:
host: localhost #服务器地址 你服务器或本机地址
port: 6379 #连接端口
database: projectdb #数据库索引,默认0
password: 123456 #密码
jedis:
pool:
max-active: 8 #连接池最大连接数(使用负值表示没有限制)
max-wait: -1 #最大阻塞等待时间(使用负值表示没有限制)
max-idle: 8 #最大空闲连接数
min-idle: 0 #最小空闲连接数
timeout: 5000 #连接超时时间(毫秒)
# 加入日志功能
mybatis-plus:
configuration:
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
# 设置MyBatis-Plus的全局配置
global-config:
db-config:
# # 设置实体类所对应的表的统一前缀
# table-prefix: t_
# 设置统一的主键生成策略
id-type: auto
6.在intercepters包下创建Logininterceptor类。
package org.example.springboot3_mybatisplus_redis_jwt.intercepters;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.example.springboot3_mybatisplus_redis_jwt.utils.JwtUtil;
import org.example.springboot3_mybatisplus_redis_jwt.utils.ThreadLocalUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import java.util.Map;
@Component
public class Logininterceptor implements HandlerInterceptor {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println(request.getRequestURI());
//令牌验证
String token = request.getHeader("cookieshopUser");
try {
ValueOperations<String, String> operations = stringRedisTemplate.opsForValue();
String redisToken = operations.get(token);
if (redisToken==null){
throw new RuntimeException();
}
Map<String, Object> claims = JwtUtil.parseToken(token);
ThreadLocalUtil.set(claims);//把业务数据储存为ThreaddLocal中
return true;
} catch (Exception e) {
response.setStatus(401);
return false;
}
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
ThreadLocalUtil.remove();
}
}
7.在pojo包下创建projectinf、Result、User类。
(1)projectinf类:
package org.example.springboot3_mybatisplus_redis_jwt.pojo; import com.baomidou.mybatisplus.annotation.TableField; import com.fasterxml.jackson.annotation.JsonFormat; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import lombok.ToString; import org.springframework.format.annotation.DateTimeFormat; import java.text.SimpleDateFormat; import java.util.Date; @Data @NoArgsConstructor @AllArgsConstructor @ToString public class projectinf { private int id; @TableField(value = "projectName") private String projectName; @TableField(value = "startDate") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") private Date startDate; @TableField(value = "endDate") @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd") private Date endDate; private int status; }
(2)Result实体类:
package org.example.springboot3_mybatisplus_redis_jwt.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; //统一响应结果 @NoArgsConstructor @AllArgsConstructor @Data public class Result<T> { private Integer code;//业务状态码 0-成功 1-失败 private String message;//提示信息 private T data;//响应数据 //快速返回操作成功响应结果(带响应数据) public static <E> Result<E> success(E data) { return new Result<>(0, "操作成功", data); } //快速返回操作成功响应结果 public static Result success() { return new Result(0, "操作成功", null); } public static Result error(String message) { return new Result(1, message, null); } }
(3)User实体类:
package org.example.springboot3_mybatisplus_redis_jwt.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @AllArgsConstructor @NoArgsConstructor @Data public class User { private Integer id; private String username; private String email; private String password; private String userpic; private String name; private String phone; private String address; private boolean isadmin; private boolean isvalidate; }
8.在utils包下创建JwtUtil、Md5Util和ThreadLocalUtil实体类。
(1)JwtUtil实体类:
package org.example.springboot3_mybatisplus_redis_jwt.utils; import com.auth0.jwt.JWT; import com.auth0.jwt.algorithms.Algorithm; import java.util.Date; import java.util.Map; public class JwtUtil { private static final String KEY = "quanfaqiang"; //接收业务数据,生成token并返回 public static String genToken(Map<String, Object> claims) { return JWT.create() .withClaim("claims", claims) .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 12)) .sign(Algorithm.HMAC256(KEY)); } //接收token,验证token,并返回业务数据 public static Map<String, Object> parseToken(String token) { return JWT.require(Algorithm.HMAC256(KEY)) .build() .verify(token) .getClaim("claims") .asMap(); } }
(2) Md5Util实体类:
package org.example.springboot3_mybatisplus_redis_jwt.utils; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; public class Md5Util { /** * 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的文件的正确性用的就是默认的这个组合 */ protected static char hexDigits[] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'}; protected static MessageDigest messagedigest = null; static { try { messagedigest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException nsaex) { System.err.println(Md5Util.class.getName() + "初始化失败,MessageDigest不支持MD5Util。"); nsaex.printStackTrace(); } } /** * 生成字符串的md5校验值 * * @param s * @return */ public static String getMD5String(String s) { return getMD5String(s.getBytes()); } /** * 判断字符串的md5校验码是否与一个已知的md5码相匹配 * * @param password 要校验的字符串 * @param md5PwdStr 已知的md5校验码 * @return */ public static boolean checkPassword(String password, String md5PwdStr) { String s = getMD5String(password); return s.equals(md5PwdStr); } public static String getMD5String(byte[] bytes) { messagedigest.update(bytes); return bufferToHex(messagedigest.digest()); } private static String bufferToHex(byte bytes[]) { return bufferToHex(bytes, 0, bytes.length); } private static String bufferToHex(byte bytes[], int m, int n) { StringBuffer stringbuffer = new StringBuffer(2 * n); int k = m + n; for (int l = m; l < k; l++) { appendHexPair(bytes[l], stringbuffer); } return stringbuffer.toString(); } private static void appendHexPair(byte bt, StringBuffer stringbuffer) { char c0 = hexDigits[(bt & 0xf0) >> 4];// 取字节中高 4 位的数字转换, >>> // 为逻辑右移,将符号位一起右移,此处未发现两种符号有何不同 char c1 = hexDigits[bt & 0xf];// 取字节中低 4 位的数字转换 stringbuffer.append(c0); stringbuffer.append(c1); } }
(3)ThreadLocalUtil 工具类:
package org.example.springboot3_mybatisplus_redis_jwt.utils; /** * ThreadLocal 工具类 */ @SuppressWarnings("all") public class ThreadLocalUtil { //提供ThreadLocal对象, private static final ThreadLocal THREAD_LOCAL = new ThreadLocal(); //根据键获取值 public static <T> T get(){ return (T) THREAD_LOCAL.get(); } //存储键值对 public static void set(Object value){ THREAD_LOCAL.set(value); } //清除ThreadLocal 防止内存泄漏 public static void remove(){ THREAD_LOCAL.remove(); } }
9.在config包下创建MybatisPlusConfig和WebConfig配置类。
(1)MybatisPlusConfig配置类:
package org.example.springboot3_mybatisplus_redis_jwt.config; import com.baomidou.mybatisplus.annotation.DbType; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }
(2)WebConfig配置类:
package org.example.springboot3_mybatisplus_redis_jwt.config; import org.example.springboot3_mybatisplus_redis_jwt.intercepters.Logininterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; @Configuration public class WebConfig implements WebMvcConfigurer { @Autowired private Logininterceptor logininterceptor; @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(logininterceptor).excludePathPatterns("/login","/register","/index"); } }
10.在mapper包下创建UserMapper和projectinfMapper接口。
(1)projectinfMapper接口:
package org.example.springboot3_mybatisplus_redis_jwt.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.example.springboot3_mybatisplus_redis_jwt.pojo.projectinf; @Mapper public interface projectinfMapper extends BaseMapper<projectinf> { }
(2)UserMapper接口:
package org.example.springboot3_mybatisplus_redis_jwt.mapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import org.apache.ibatis.annotations.Mapper; import org.example.springboot3_mybatisplus_redis_jwt.pojo.User; @Mapper public interface UserMapper extends BaseMapper<User> { }
11.在controller包下创建FileUploadController、projectinfController和UserController控制类。其中FileUploadController类为文件上传、projectinfController类有申报查询全部列表、根据申报项目名查找、删除、批量删除、搜索、修改、添加功能。UserController类有登录、注册、注销、根据id删除、添加、修改密码、搜索等功能。
(1) FileUploadController控制类:
package org.example.springboot3_mybatisplus_redis_jwt.controlller; import org.example.springboot3_mybatisplus_redis_jwt.pojo.Result; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.system.ApplicationHome; import org.springframework.core.env.Environment; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.multipart.MultipartFile; import java.io.File; import java.io.IOException; import java.util.UUID; //@CrossOrigin(origins = {"*","null"}) @RestController public class FileUploadController { @Autowired private Environment env; @PostMapping("/upload") public Result saveFile(MultipartFile file) { System.out.println("上传文件运行了"); if (file.isEmpty()) { return Result.success("文件为空!"); } // 给文件重命名 String fileName = UUID.randomUUID() + "." + file.getContentType() .substring(file.getContentType().lastIndexOf("/") + 1); System.out.println(fileName+"==============="); try { // 获取保存路径 String path = getSavePath(); File files = new File(path, fileName); System.out.println(files); File parentFile = files.getParentFile(); System.out.println(parentFile); if (!parentFile.exists()) { parentFile.mkdir(); } file.transferTo(files); } catch (IOException e) { e.printStackTrace(); } // return Result.success(fileName); // 返回重命名后的文件名 // return Result.success("http://localhost:9095/upload/"+fileName); // 返回重命名后的文件名 System.out.println(getPort()); return Result.success("http://localhost:"+getPort()+"/upload/"+fileName); // 返回重命名后的文件名 } public String getSavePath() { // 这里需要注意的是ApplicationHome是属于SpringBoot的类 // 获取项目下resources/static/img路径 ApplicationHome applicationHome = new ApplicationHome(this.getClass()); // 保存目录位置根据项目需求可随意更改 return applicationHome.getDir().getParentFile() .getParentFile().getAbsolutePath() + "\\src\\main\\resources\\upload"; } public int getPort() { // 获取当前应用绑定的端口号 return env.getProperty("local.server.port", Integer.class); } }
(2)projectinfController控制类:
package org.example.springboot3_mybatisplus_redis_jwt.controlller; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.annotation.JsonFormat; import org.example.springboot3_mybatisplus_redis_jwt.pojo.Result; import org.example.springboot3_mybatisplus_redis_jwt.pojo.projectinf; import org.example.springboot3_mybatisplus_redis_jwt.service.projectinfService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.format.annotation.DateTimeFormat; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.util.Date; import java.util.List; @RestController @RequestMapping("/project") public class projectinfController { @Autowired private projectinfService service; @GetMapping("all") public Result getAll(Integer currentPage, Integer pageSize){ System.out.println(currentPage+":"+pageSize); Page<projectinf> all = service.getAll(currentPage, pageSize); System.out.println(all.getRecords()); return Result.success(all); } @DeleteMapping("/delete/{id}") public Result delete(@PathVariable Integer id) { service.delete(id); return Result.success("删除成功"); } @DeleteMapping("/deleteMore/{ids}") public Result deleteIds(@PathVariable List<Integer> ids) { System.out.println(ids+"sssssssssssssssssss"); service.deleteIds(ids); return Result.success("删除成功"); } @GetMapping("/search") public Result searchGoodsTypeName( String name, String status, Integer currentPage, Integer pageSize,String start,String end) { System.out.println(start + "sssssssssssssssssssssssssssssss"); Page<projectinf> typeList = service.search(start,end,name,status, currentPage, pageSize); return Result.success(typeList); } @PostMapping("/update") public Result update(@RequestBody projectinf projectinf) { System.out.println(projectinf); int update = service.update(projectinf); if(update>0){ System.out.println("审核成功"); } return Result.success(update); } @GetMapping("/selectProjectName") public Result selectTypeName(String projectName) { Boolean flag = service.selectName(projectName); System.out.println(flag); return Result.success(flag); } @PostMapping("/add") public Result add(@RequestBody projectinf projectinf) { System.out.println(projectinf); int add = service.add(projectinf); if (add>0){ System.out.println("添加申报成功"); } return Result.success(add); } }
(3)UserController控制类:
package org.example.springboot3_mybatisplus_redis_jwt.controlller; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.example.springboot3_mybatisplus_redis_jwt.pojo.Result; import org.example.springboot3_mybatisplus_redis_jwt.pojo.User; import org.example.springboot3_mybatisplus_redis_jwt.service.UserService; import org.example.springboot3_mybatisplus_redis_jwt.utils.JwtUtil; import org.example.springboot3_mybatisplus_redis_jwt.utils.Md5Util; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.data.redis.core.ValueOperations; import org.springframework.web.bind.annotation.*; import java.util.HashMap; import java.util.List; @RestController @RequestMapping public class UserController { @Autowired private UserService userService; @Autowired private StringRedisTemplate stringRedisTemplate; @GetMapping("/SelectAll") public Result SelectAll(int currentPage, int pageSize) { Page<User> user = userService.SelectAll(currentPage, pageSize); System.out.println(user); return Result.success(user); } @PostMapping("/login") public Result login(String username, String password) { System.out.println(username + ":" + password); User loginuser = userService.findByUserName(username); if (loginuser == null) { return Result.error("用户名不存在"); } if (Md5Util.getMD5String(password).equals(loginuser.getPassword())) { HashMap<String, Object> stringObjectHashMap = new HashMap<>(); stringObjectHashMap.put("id", loginuser.getId()); stringObjectHashMap.put("username", loginuser.getUsername()); String token = JwtUtil.genToken(stringObjectHashMap); //把token存储到redisz中 ValueOperations<String, String> operations = stringRedisTemplate.opsForValue(); operations.set(token,token/*, TimeUnit.HOURS*/); return Result.success(token); } return Result.error("密码错误"); } @PostMapping("/register") public Result register(String username, String password) { User u = userService.findByUserName(username); System.out.println(u); if (u == null) { userService.register(username, password); return Result.success("注册成功"); } else { return Result.error("用户名已被占用"); } } @PostMapping("/logoff") public Result logoff(String username, String password) { boolean logoff = userService.logoff(username, password); System.out.println(logoff); if (logoff) { System.out.println("注销成功"); }else { System.out.println("用户名或密码不正确"); } return Result.success(logoff); } @DeleteMapping("/deleteId/{id}") public Result<String> DeleteId(@PathVariable Integer id) { userService.DeleteId(id); return Result.success("删除成功"); } @DeleteMapping("/deleteMore/{ids}") public Result deleteIds(@PathVariable List<Integer> ids) { System.out.println(ids+"sssssssssssssssssss"); userService.deleteIds(ids); return Result.success("商品批量删除成功"); } @GetMapping("/selectId") public Result SelectId(@RequestParam Integer id) { User user = userService.SelectId(id); System.out.println(id); System.out.println(user); return Result.success(user); } @GetMapping("/selectName") public Result selectName(String username) { User loginuser = userService.findByUserName(username); System.out.println(loginuser); return Result.success(loginuser); } @PostMapping("/add") public Result add(@RequestBody User user) { System.out.println(user); userService.Add(user); return Result.success("添加成功"); } @GetMapping("/selectPassword") public Result selectpassword(String password, Integer id) { System.out.println(password+":"+id); Boolean flag = userService.selectPassword(password, id); System.out.println(flag); if (flag == true) { System.out.println("校验成功"); }else { System.out.println("校验失败"); } return Result.success(flag); } @PutMapping("/update") public Result update(@RequestBody User user) { System.out.println(user); userService.update(user); return Result.success("修改成功"); } @PatchMapping("/updatePwd") public Result updatePwd(String password, Integer id) { System.out.println(password+":"+id); userService.updatePwd(password, id); return Result.success("密码修改成功"); } @PatchMapping("/updateUserAddress") public Result updateUserAddress(String UserAddress, int id) { userService.updateUserAddress(UserAddress, id); return Result.success(id + "修改地址成功"); } @GetMapping("/userInfo") public Result<User> userInfo(/*@RequestHeader(name="Authorization") String token*/) { // Map<String, Object> map = JwtUtil.parseToken(token); // String username=(String)map.get("username"); // // Map<String, Object> map = ThreadLocalUtil.get(); // System.out.println(map); // String username = (String) map.get("username"); // User user = userService.findByUserName(username); // return Result.success(user); ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue(); String username1 = stringStringValueOperations.get("username"); User user1 = userService.findByUserName(username1); return Result.success(user1); } @PatchMapping("/updateAvatar") public Result updateAvatar(@RequestParam String avatarUrl){ System.out.println(avatarUrl); userService.updateAvatar(avatarUrl); return Result.success("更新用户头像完成"); } @GetMapping("/serchUserName") public Result searchGoods(String userName,Integer currentPage, Integer pageSize){ Page<User> userList=userService.searchUser(userName,currentPage,pageSize); return Result.success(userList); } @GetMapping("/selectUsername") public Result selectUsername(String username){ System.out.println(username); Boolean flag=userService.selectUsername(username); System.out.println(flag); return Result.success(flag); } }
12.在service包下创建projectinfService和UserService服务接口类,还有impl包:
(1)projectinfService服务接口类:
package org.example.springboot3_mybatisplus_redis_jwt.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.example.springboot3_mybatisplus_redis_jwt.pojo.projectinf; import java.util.List; public interface projectinfService { Page<projectinf> getAll(Integer currentPage, Integer pageSize); void delete(Integer id); void deleteIds(List<Integer> ids); Page<projectinf> search(String date, String end,String name, String status, Integer currentPage, Integer pageSize); int update(projectinf projectinf); int add(projectinf projectinf); Boolean selectName(String projectName); }
(2)UserService服务接口类:
package org.example.springboot3_mybatisplus_redis_jwt.service; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.example.springboot3_mybatisplus_redis_jwt.pojo.User; import java.util.List; public interface UserService { Page<User> SelectAll(int currentPage, int pageSize); User findByUserName(String username); void register(String username, String password); void updateAvatar(String avatarUrl); Page<User> searchUser(String userName, Integer currentPage, Integer pageSize); Boolean selectUsername(String username); void updateUserAddress(String userAddress, int id); void updatePwd(String password, Integer id); void update(User user); Boolean selectPassword(String password, Integer id); void Add(User user); void DeleteId(Integer id); void deleteIds(List<Integer> ids); boolean logoff(String username, String password); User SelectId(Integer id); }
13.在impl包下创建projectinfServiceImpl、UserServiceImpl服务实现类。
(1)projectinfServiceImpl服务实现类:
package org.example.springboot3_mybatisplus_redis_jwt.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.example.springboot3_mybatisplus_redis_jwt.mapper.projectinfMapper; import org.example.springboot3_mybatisplus_redis_jwt.pojo.projectinf; import org.example.springboot3_mybatisplus_redis_jwt.service.projectinfService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.web.bind.annotation.RequestParam; import java.util.List; @Service public class projectinfServiceImpl implements projectinfService { @Autowired private projectinfMapper mapper; @Override public Page<projectinf> getAll(@RequestParam(name = "currentPage", defaultValue = "1") Integer currentPage, @RequestParam(name = "pageSize", defaultValue = "5")Integer pageSize) { Page<projectinf> projectinfPage = new Page<>(currentPage, pageSize); QueryWrapper<projectinf> queryWrapper = new QueryWrapper<>(); queryWrapper.orderByAsc("id"); Page<projectinf> projectinfPage1 = mapper.selectPage(projectinfPage, queryWrapper); projectinfPage1.getRecords().forEach(System.out::println); projectinfPage1.getCurrent(); projectinfPage1.getSize(); projectinfPage1.getPages(); return projectinfPage; } @Override public void delete(Integer id) { mapper.deleteById(id); } @Override public void deleteIds(List<Integer> ids) { mapper.deleteBatchIds(ids); } @Override public Page<projectinf> search( String start,String end, String name, String status, Integer currentPage, Integer pageSize) { Page<projectinf> projectinfPage = new Page<>(currentPage, pageSize); QueryWrapper<projectinf> queryWrapper = new QueryWrapper<>(); if (start != null&&!start.equals("")) { queryWrapper.ge("startDate", start); } if (end!=null&&!end.equals("")) { queryWrapper.le("endDate", end); } if (name!=null&&!name.equals("")) { queryWrapper.like("projectName", name); } if (status!=null&&!status.equals("")) { queryWrapper.eq("status", status); } Page<projectinf> projectinfPage1 = mapper.selectPage(projectinfPage, queryWrapper); return projectinfPage1; } @Override public int update(projectinf projectinf) { int i = mapper.updateById(projectinf); return i; } @Override public int add(projectinf inf) { int i = mapper.insert(inf); return i; } @Override public Boolean selectName(String projectName) { List<projectinf> typeList = mapper.selectList(new QueryWrapper<projectinf>().eq("projectName", projectName)); if (typeList != null && typeList.size() > 0) { return true; } return false; } }
(2) UserServiceImpl 服务实现类:
package org.example.springboot3_mybatisplus_redis_jwt.service.impl; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import org.example.springboot3_mybatisplus_redis_jwt.mapper.UserMapper; import org.example.springboot3_mybatisplus_redis_jwt.pojo.User; import org.example.springboot3_mybatisplus_redis_jwt.service.UserService; import org.example.springboot3_mybatisplus_redis_jwt.utils.Md5Util; import org.example.springboot3_mybatisplus_redis_jwt.utils.ThreadLocalUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; @Service public class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public Page<User> SelectAll(int currentPage, int pageSize) { Page<User> page = new Page<>(currentPage, pageSize); QueryWrapper<User> userQueryWrapper = new QueryWrapper<>(); userQueryWrapper.orderByAsc("id"); Page<User> userPage = userMapper.selectPage(page, userQueryWrapper); System.out.println("当前页:" + userPage.getCurrent()); System.out.println("每页记录数:" + userPage.getSize()); System.out.println("总记录数:" + userPage.getTotal()); System.out.println("总页数:" + userPage.getPages()); List<User> userPageList = userPage.getRecords(); userPageList.forEach(System.out::println); return userPage; } @Override public User findByUserName(String username) { return userMapper.selectOne(new QueryWrapper<User>().eq("username", username)); } @Override public void register(String username, String password) { String md5String = Md5Util.getMD5String(password); User user = new User(); user.setUsername(username); user.setPassword(md5String); userMapper.insert(user); } @Override public boolean logoff(String username, String password) { int delete = userMapper.delete(new QueryWrapper<User>().eq("username", username).eq("password", Md5Util.getMD5String(password))); if (delete > 0) { return true; } return false; } @Override public void DeleteId(Integer id) { userMapper.deleteById(id); } @Override public void deleteIds(List<Integer> ids) { userMapper.deleteBatchIds(ids); } @Override public User SelectId(Integer id) { return userMapper.selectOne(new QueryWrapper<User>().eq("id", id)); } @Override public void Add(User user) { user.setPassword(Md5Util.getMD5String(user.getPassword())); userMapper.insert(user); } @Override public Boolean selectPassword(String password, Integer id) { User user = userMapper.selectById(id); if (user != null) { if (Md5Util.getMD5String(password).equals(user.getPassword())) { return true; } return false; } return false; } @Override public void update(User user) { user.setPassword(Md5Util.getMD5String(user.getPassword())); userMapper.updateById(user); } @Override public void updatePwd(String password, Integer id) { System.out.println(password+":"+id); String md5String = Md5Util.getMD5String(password); LambdaUpdateWrapper<User> userLambdaUpdateWrapper = new LambdaUpdateWrapper<>(); userLambdaUpdateWrapper.eq(User::getId, id); userLambdaUpdateWrapper.set(User::getPassword, md5String); userMapper.update(userLambdaUpdateWrapper); } @Override public void updateUserAddress(String userAddress, int id) { LambdaUpdateWrapper<User> eq = new LambdaUpdateWrapper<User>().eq(User::getAddress, userAddress).eq(User::getId, id); System.out.println(eq); userMapper.update(eq); } @Override public void updateAvatar(String avatarUrl) { Map<String, Object> map = ThreadLocalUtil.get(); Integer id = (Integer) map.get("id"); System.out.println(id+":"+avatarUrl); LambdaUpdateWrapper<User> userLambdaUpdateWrapper = new LambdaUpdateWrapper<>(); userLambdaUpdateWrapper.eq(User::getId, id); userLambdaUpdateWrapper.set(User::getUserpic, avatarUrl); userMapper.update(userLambdaUpdateWrapper); System.out.println("==============="); } @Override public Page<User> searchUser(String userName, Integer currentPage, Integer pageSize) { Page<User> page = new Page<>(currentPage, pageSize); QueryWrapper<User> queryWrapper = new QueryWrapper<>(); Page<User> userPage = userMapper.selectPage(page, queryWrapper.like("username",userName)); return userPage; } @Override public Boolean selectUsername(String username) { List<User> username1 = userMapper.selectList(new QueryWrapper<User>().eq("username", username)); System.out.println(username1); if (username1.size() > 0) { return true; }else { return false; } } }
14.后端最终目录
三、前端(vue3+elementplus+pinia+html5)
1.创建vue3项目。
任意创建一个目录,在该目录下的路径上cmd回车进入命令行界面,然后运行npm init vue@latest命令回车,输入y再回车,然后除了pinia选是外其它全部选否回车。
2.删除非必要组件,比如HelloWord.vue相关组件等。
3.安装mybatisplus以及插件。
npm install element-plus --save
npm install axios
npm install sass -D
npm install pinia-persistedstate-plugin
4.修改main.js
import './assets/main.scss' import { createApp } from 'vue' import { createPinia } from 'pinia' import { createPersistedState } from 'pinia-plugin-persistedstate'; import App from './App.vue' import router from './router' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import locale from "element-plus/dist/locale/zh-cn.js"; const app = createApp(App) const pinia = createPinia(); const persist = createPersistedState(); pinia.use(persist); app.use(pinia) app.use(router) app.use(ElementPlus, { locale });//这是国际化配置,比如分页时候 // app.use(ElementPlus) app.mount('#app')
5.修改App.vue
<script setup> </script> <template> <RouterView /> </template> <style scoped> </style>
6.修改vite.config.js(添加前后端跨越配置)
import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' // https://vitejs.dev/config/ export default defineConfig({ plugins: [ vue(), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } }, server: { proxy: { "/api": { target: "http://localhost:9095/", // 后端服务器地址 changeOrigin: true, // 是否改变请求域名 rewrite: (path) => path.replace(/^\/api/, ""), //将原有请求路径中的api替换为'' }, }, }, })
7.在src包下创建utils包,而且创建request.js文件
/定制请求的实例 //导入axios npm install axios import axios from 'axios'; import { ElMessage } from 'element-plus' //定义一个变量,记录公共的前缀 , baseURL // const baseURL = 'http://localhost:9095'; const baseURL = '/api'; const instance = axios.create({ baseURL }) import { useTokenStore } from '@/stores/token.js' //添加请求拦截器 instance.interceptors.request.use( (config) => { //请求前的回调 //添加token const tokenStore = useTokenStore(); //判断有没有token if (tokenStore.token) { config.headers.cookieshopUser = tokenStore.token } return config; }, (err) => { //请求错误的回调 Promise.reject(err) } ) /* import {useRouter} from 'vue-router' const router = useRouter(); */ import router from '@/router' //添加响应拦截器 instance.interceptors.response.use( result => { return result.data; //判断业务状态码 // if (result.data.code === 0) { // return result.data; // } // //操作失败 // //alert(result.data.msg?result.data.msg:'服务异常') // ElMessage.error(result.data.msg ? result.data.msg : '服务异常') // //异步操作的状态转换为失败 // return Promise.reject(result.data) }, err => { //判断响应状态码,如果为401,则证明未登录,提示请登录,并跳转到登录页面 if (err.response.status === 401) { ElMessage.error('请先登录') router.push('/login') } else { ElMessage.error('服务异常') } return Promise.reject(err);//异步的状态转化成失败的状态 } ) export default instance;
8.在src包下创建stores包,而且创建token.js和useInfo.js文件。
(1)token.js:文件
import { defineStore } from "pinia"; import { ref } from "vue"; export const useTokenStore = defineStore( "token", () => { const token = ref(""); const setToken = (newToken) => { token.value = newToken; }; const removeToken = () => { token.value = ""; }; return { token, setToken, removeToken }; }, { persist: true, } );
(2)useInfo.js文件:
import { defineStore } from "pinia"; import { ref } from "vue"; export const useUserInfoStore = defineStore( "userInfo", () => { //1.定义用户信息 const info = ref({}); //2.定义修改用户信息的方法 const setInfo = (newInfo) => { info.value = newInfo; }; //3.定义清空用户信息的方法 const removeInfo = () => { info.value = {}; }; return { info, setInfo, removeInfo }; }, { persist: true, } );
9.在assets包下创建mian.scss全局样式(在此包下还有添加图片,该步骤省略)
body { margin: 0; background-color: #f5f5f5; } /* fade-slide */ .fade-slide-leave-active, .fade-slide-enter-active { transition: all 0.3s; } .fade-slide-enter-from { transform: translateX(-30px); opacity: 0; } .fade-slide-leave-to { transform: translateX(30px); opacity: 0; }
10.在src包下创建api包,而且创建classes.js和user.js文件。
(1)classes.js文件:
import request from "@/utils/request.js"; //获取列表 export const classesInfoGetService = (params) => { return request.get("/project/all", { params: params }); }; //判断项目名称是否存在 export const classesNameGetService = (params) => { return request.get("/project/selectProjectName?projectName=" + params); }; //获取搜索列表 export const classesSerchNameGetService = (params) => { return request.get("/project/search", { params: params }); }; //添加 export const classesAddService = (classesModel) => { return request.post("/project/add", classesModel); }; //修改 export const classesUpdateService = (classesModel) => { return request.post("/project/update", classesModel); }; //删除 export const classesDeleteService = (id) => { return request.delete("/project/delete/" + id); }; //批量删除 export const classesDeletemoreService = (ids) => { return request.delete("/project/deleteMore/" + ids); };
(2)user.js文件:
import request from "@/utils/request.js"; //注册 export const userRegisterService = (registerData) => { const params = new URLSearchParams(); for (let key in registerData) { params.append(key, registerData[key]); } return request.post("/register", params); }; //登录 export const userLoginService = (loginData) => { const params = new URLSearchParams(); for (let key in loginData) { params.append(key, loginData[key]); } return request.post("/login", params); }; //注销 export const userLogoffService = (logoff) => { const params = new URLSearchParams(); for (let key in logoff) { params.append(key, logoff[key]); } return request.post("/logoff", params); }; //获取个人信息 export const userInfoGetService = () => { return request.get("/userInfo"); }; //检查密码和原密码是否相同 export const CheckUserPwdGetService = (params) => { return request.get("/selectPassword", { params: params }); }; //修改个人信息 export const userInfoUpdateService = (userInfo) => { return request.put("/update", userInfo); }; //修改头像 export const userAvatarUpdateService = (avatarUrl) => { let params = new URLSearchParams(); params.append("avatarUrl", avatarUrl); return request.patch("/updateAvatar", params); }; //修改密码 export const userPwdUpdateService = (param) => { let params = new URLSearchParams(); for (let key in param) { params.append(key, param[key]) } return request.patch("/updatePwd", params); }; //获取客户列表 export const userCoustomGetService = (params) => { return request.get("/SelectAll", { params: params }); }; //获取搜索列表 export const coustomSerchNameGetService = (params) => { return request.get("/serchUserName", { params: params }); }; //添加客户 export const coustomAddService = (coustomModel) => { return request.post("/add", coustomModel); }; //修改分类 export const coustomUpdateService = (coustomModel) => { return request.put("/update", coustomModel); }; //删除客户 export const coustomDeleteService = (id) => { return request.delete("/deleteId/" + id); }; //批量删除分类 export const coustomDeleteMoreService = (ids) => { return request.delete("/deleteMore/" + ids); }; //判断客户名是否存在 export const coustomNameGetService = (params) => { return request.get("/selectUsername?username=" + params); };
11.在views包下创建project包和user包。而且在project包创建All.vue文件;在user包下创建LogOff.vue、UserAvatar.vue、UserInfo.vue|、UserResetPassword.vue;在views包下Layout.vue和Login.vue
(1)Layout.vue:
<script setup> import { Management, Promotion, UserFilled, User, Crop, EditPen, SwitchButton, CaretBottom, } from "@element-plus/icons-vue"; //默认未加载图 import avatar from "@/assets/default.png"; //导入接口函数 import { userInfoGetService } from "@/api/user.js"; //导入pinia import { useUserInfoStore } from "@/stores/userInfo.js"; const userInfoStore = useUserInfoStore(); //获取个人信息 const getUserInfo = async () => { let result = await userInfoGetService(); //存储pinia // userInfoStore.info = result.data; userInfoStore.setInfo(result.data); }; getUserInfo(); //dropDown条目被点击后,回调的函数 import { useRouter } from "vue-router"; const router = useRouter(); import { ElMessage, ElMessageBox } from "element-plus"; import { useTokenStore } from "@/stores/token.js"; const tokenStore = useTokenStore(); const handleCommand = (command) => { if (command === "logout") { //退出登录 ElMessageBox.confirm("你确认退出登录码?", "温馨提示", { confirmButtonText: "确认", cancelButtonText: "取消", type: "warning", }) .then(async () => { //用户点击了确认 //清空pinia中的token和个人信息 // userInfoStore.info = {}; // tokenStore.token = ""; tokenStore.removeToken(); userInfoStore.removeInfo(); //跳转到登录页 router.push("/login"); }) .catch(() => { //用户点击了取消 ElMessage({ type: "info", message: "取消退出", }); }); } else { //路由 router.push(command); } }; </script> <template> <el-container class="layout-container"> <!-- 右侧主区域 --> <el-container> <!-- 头部区域 --> <el-header> <div> 管理员<strong>{{ userInfoStore.info.username ? userInfoStore.info.username : userInfoStore.info.name }}</strong> </div> <el-dropdown placement="bottom-end" @command="handleCommand"> <span class="el-dropdown__box"> <el-avatar :src=" userInfoStore.info.userpic ? userInfoStore.info.userpic : avatar " /> <el-icon> <CaretBottom /> </el-icon> </span> <template #dropdown> <el-dropdown-menu> <el-dropdown-item command="/user/info" :icon="User" >基本资料 </el-dropdown-item> <el-dropdown-item command="/user/avatar" :icon="Crop" >更换头像 </el-dropdown-item> <el-dropdown-item command="/user/repwd" :icon="EditPen" >重置密码 </el-dropdown-item> <el-dropdown-item command="/user/logoff" :icon="EditPen" >账号注销 </el-dropdown-item> <el-dropdown-item command="/all" :icon="Crop" >后台管理 </el-dropdown-item> <el-dropdown-item command="logout" :icon="SwitchButton" >退出登录 </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </el-header> <!-- 中间区域 --> <el-main> <!-- <div style="width: 1290px; height: 570px; border: 1px solid red"> 内容展示区 </div> --> <router-view /> </el-main> <!-- 底部区域 --> <el-footer>申报项目信息管理系统 ©2024</el-footer> </el-container> </el-container> </template> <style lang="scss" scoped> .layout-container { height: 100vh; .el-aside { background-color: #232323; &__logo { height: 120px; background: url("@/assets/logo.png") no-repeat center / 120px auto; } .el-menu { border-right: none; } } .el-header { background-color: #fff; display: flex; align-items: center; justify-content: space-between; .el-dropdown__box { display: flex; align-items: center; .el-icon { color: #999; margin-left: 10px; } &:active, &:focus { outline: none; } } } .el-footer { display: flex; align-items: center; justify-content: center; font-size: 14px; color: #666; } } </style>
(2)Login.vue:
<script setup> import { User, Lock } from "@element-plus/icons-vue"; import { ref } from "vue"; import { ElMessage } from "element-plus"; //控制注册与登录表单的显示, 默认显示注册 const isRegister = ref(false); const registerData = ref({ username: "", password: "", rePassword: "", }); const checkRePassword = (rule, value, callback) => { if (value === "") { callback(new Error("请确认密码")); } else if (value !== registerData.value.password) { callback(new Error("请确保两次输入密码一样")); } else { callback(); } }; const rules = { username: [ { required: true, message: "请输入用户名", trigger: "blur" }, { min: 2, max: 16, message: "长度为5~16为非空字符", trigger: "blur" }, ], password: [ { required: true, message: "请输入密码", trigger: "blur" }, { min: 5, max: 16, message: "长度为5~16为非空字符", trigger: "blur" }, ], rePassword: [{ validator: checkRePassword, trigger: "blur" }], }; import { userRegisterService, userLoginService } from "@/api/user.js"; const register = async () => { let result = await userRegisterService(registerData.value); if (result.code === 0) { return ElMessage.success("注册成功"); } if (result.message === "用户名已被占用") { return ElMessage.warning("用户名已被占用"); } ElMessage.error("注册失败"); }; import { useTokenStore } from "@/stores/token.js"; import { useRouter } from "vue-router"; const tokenStore = useTokenStore(); const router = useRouter(); const login = async () => { let result = await userLoginService(registerData.value); console.log(result); if (result.code === 0) { router.push("/"); console.log(result.data); tokenStore.setToken(result.data); return ElMessage.success("登录成功"); } if (result.message === "用户名不存在") { return ElMessage.warning("用户名不存在"); } if (result.message === "密码错误") { return ElMessage.error("密码错误"); } ElMessage.error("登录失败"); }; const clearRegisterData = () => { registerData.value = { username: "", password: "", rePassword: "", }; }; </script> <template> <el-row class="login-page"> <el-col :span="12" class="bg"></el-col> <el-col :span="6" :offset="3" class="form"> <!-- 注册表单 --> <el-form ref="form" size="large" autocomplete="off" v-if="isRegister" :model="registerData" :rules="rules" > <el-form-item> <h1>注册</h1> </el-form-item> <el-form-item prop="username"> <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username" ></el-input> </el-form-item> <el-form-item prop="password"> <el-input :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password" ></el-input> </el-form-item> <el-form-item prop="rePassword"> <el-input :prefix-icon="Lock" type="password" placeholder="请输入再次密码" v-model="registerData.rePassword" ></el-input> </el-form-item> <!-- 注册按钮 --> <el-form-item> <el-button class="button" type="primary" auto-insert-space @click="register()" > 注册 </el-button> </el-form-item> <el-form-item class="flex"> <el-link type="info" :underline="false" @click=" isRegister = false; clearRegisterData(); " > ← 返回 </el-link> </el-form-item> </el-form> <!-- 登录表单 --> <el-form ref="form" size="large" autocomplete="off" v-else :model="registerData" :rules="rules" > <el-form-item> <h1>登录</h1> </el-form-item> <el-form-item prop="username"> <el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.username" ></el-input> </el-form-item> <el-form-item prop="password"> <el-input name="password" :prefix-icon="Lock" type="password" placeholder="请输入密码" v-model="registerData.password" ></el-input> </el-form-item> <el-form-item class="flex"> <div class="flex"> <el-checkbox>记住我</el-checkbox> <el-link type="primary" :underline="false">忘记密码?</el-link> </div> </el-form-item> <!-- 登录按钮 --> <el-form-item> <el-button class="button" type="primary" auto-insert-space @click="login" >登录</el-button > </el-form-item> <el-form-item class="flex"> <el-link type="info" :underline="false" @click=" isRegister = true; clearRegisterData(); " > 注册 → </el-link> </el-form-item> </el-form> </el-col> </el-row> </template> <style lang="scss" scoped> /* 样式 */ .login-page { height: 100vh; background-color: #fff; .bg { background: url("@/assets/login_bg.jpg") no-repeat center / cover; border-radius: 0 20px 20px 0; } .form { display: flex; flex-direction: column; justify-content: center; user-select: none; .title { margin: 0 auto; } .button { width: 100%; } .flex { width: 100%; display: flex; justify-content: space-between; } } } </style>
(3)All.vue:
<script lang="ts" setup> import { Edit, Delete } from "@element-plus/icons-vue"; import { ref } from "vue"; const classesTable = ref([]); //分页条数据模型 const pageNum = ref(1); //当前页 const total = ref(20); //总条数 const pageSize = ref(10); //每页条数 const pageTotle = ref(12); //总页数 //当每页条数发生了变化,调用此函数 const onSizeChange = (size) => { pageSize.value = size; getClassesList(); }; //当前页码发生变化,调用此函数 const onCurrentChange = (num) => { pageNum.value = num; getClassesList(); }; //获取数据 import { classesInfoGetService, classesNameGetService, classesAddService, classesUpdateService, classesDeleteService, classesDeletemoreService, classesSerchNameGetService, } from "@/api/classes.js"; const getClassesList = async () => { let params = { currentPage: pageNum.value, pageSize: pageSize.value, }; let result = await classesInfoGetService(params); console.log(result.data); classesTable.value = result.data.records; pageNum.value = result.data.current; pageSize.value = result.data.size; total.value = result.data.total; pageTotle.value = result.data.pages; }; getClassesList(); // 隔行变色的函数 const tableRowClassName = ({ row, rowIndex }) => { return rowIndex % 2 === 0 ? "even-row" : "odd-row"; }; //控制添加弹窗 const dialogVisible = ref(false); //添加数据模型 const classesModel = ref({ id: 1, projectName: "", startDate: Date, endDate: Date, status: "", }); //访问后台,添加文章分类 import { ElMessage } from "element-plus"; const addclasses = () => { if (formRef.value) { formRef.value.validate(async (valid, fields) => { if (valid) { console.log("submit!"); let returnflag = await classesNameGetService( classesModel.value.projectName ); console.log( JSON.stringify(returnflag) + "ddddddddddddddddd" + returnflag.data ); if (returnflag.data) { return ElMessage.error("该申报已添加"); } let result = await classesAddService(classesModel.value); // console.log(result); console.log(result.data); if (result.data > 0) { ElMessage.success("申报添加成功"); } else { ElMessage.error("申报添加失败"); } //隐藏弹窗 dialogVisible.value = false; //再次访问后台接口,查询所有分类 getClassesList(); } else { console.error("error submit!", fields); ElMessage.error("不能为空!"); // 可以在这里处理表单错误,比如显示错误提示 } }); } }; const title = ref(""); // 修改回显 const showDialog = (row) => { title.value = "申报项目信息"; dialogVisible.value = true; //将row中的数据赋值给classesModel classesModel.value.projectName = row.projectName; //修改的时候必须传递分类的id,所以扩展一个id属性 classesModel.value.id = row.id; classesModel.value.startDate = row.startDate; classesModel.value.endDate = row.endDate; if (row.status === 0) { classesModel.value.status = "已申报"; } else if (row.status === 1) { classesModel.value.status = "审核中"; } else { classesModel.value.status = "已审核"; } }; const formRef = ref(null); //修改 const updateClasses = () => { if (formRef.value) { formRef.value.validate(async (valid, fields) => { if (valid) { console.log("submit!"); if (classesModel.value.status === "已申报") { classesModel.value.status = "0"; } else if (classesModel.value.status === "审核中") { classesModel.value.status = "1"; } else if (classesModel.value.status === "已审核") { classesModel.value.status = "2"; } console.log(classesModel.value.startDate); let result = await classesUpdateService(classesModel.value); if (result.data > 0) { ElMessage.success("审核成功"); } else { ElMessage.error("审核失败"); } //隐藏弹窗 dialogVisible.value = false; //再次访问后台接口,查询所有分类 getClassesList(); } else { console.error("error submit!", fields); ElMessage.error("不能为空!"); // 可以在这里处理表单错误,比如显示错误提示 } }); } }; //清空模型数据 const clearClassesModel = () => { (classesModel.value.id = null), (classesModel.value.projectName = ""), (classesModel.value.startDate = null), (classesModel.value.endDate = null), (classesModel.value.status = ""); }; import { ElMessageBox } from "element-plus"; //删除分类 const deleteClasses = (row) => { ElMessageBox.confirm("你确认删除该信息吗?", "温馨提示", { confirmButtonText: "确认", cancelButtonText: "取消", type: "warning", }) .then(async () => { //用户点击了确认 let result = await classesDeleteService(row.id); ElMessage.success("删除成功"); getClassesList(); }) .catch(() => { //用户点击了取消 ElMessage({ type: "info", message: "取消删除", }); }); }; //表格上选择多选框 const selectedRows = ref([]); const handleSelectionChange = (val) => { selectedRows.value = val; }; const ids = ref([]); const deleteClassesMore = () => { if (selectedRows.value.length > 0) { ElMessageBox.confirm("你确认批量删除该信息?", "温馨提示", { confirmButtonText: "确认", cancelButtonText: "取消", type: "warning", }) .then(async () => { selectedRows.value.forEach((row) => { console.log(row.id); ids.value.push(row.id); }); console.log(ids.value); console.log(ids.value[1]); let result = await classesDeletemoreService(ids.value); ElMessage.success("批量删除成功"); ids.value = []; getClassesList(); }) .catch(() => { //用户点击了取消 ElMessage({ type: "info", message: "取消删除", }); }); } else { ElMessage.error("您还未选择删除分类"); } }; //搜索 const searchClassesName = ref(""); const searchClassesstatus = ref(""); const searchClassesstartDate = ref(""); const searchClassesendDate = ref(""); const searchClassesbuttom = async () => { let params = { status: searchClassesstatus.value, start: searchClassesstartDate.value, end: searchClassesendDate.value, name: searchClassesName.value, currentPage: pageNum.value, pageSize: pageSize.value, }; let result = await classesSerchNameGetService(params); console.log(result.data); classesTable.value = result.data.records; pageNum.value = result.data.current; pageSize.value = result.data.size; total.value = result.data.total; pageTotle.value = result.data.pages; }; </script> <template> <el-card class="page-container"> <template #header> <div class="header"> <span>搜索栏</span> <div> <el-input v-model="searchClassesName" placeholder="项目名称" clearable style="width: 330px" ></el-input> <el-select v-model="searchClassesstatus" placeholder="申报状态" clearable style="width: 130px; padding: 0px 20px" > <el-option label="全部" value="" /> <el-option label="已申报" value="0" /> <el-option label="审核中" value="1" /> <el-option label="已审核" value="2" /> </el-select> <el-date-picker v-model="searchClassesstartDate" type="date" placeholder="日期范围开始" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /> <el-date-picker v-model="searchClassesendDate" type="date" placeholder="日期范围结束" format="YYYY-MM-DD" value-format="YYYY-MM-DD" /> <el-button type="primary" @click="searchClassesbuttom" >搜索</el-button > <el-button type="primary" @click=" (dialogVisible = true), (title = '添加申报'); //逗号,分号都可以 clearClassesModel(); " >添加申报</el-button ><el-button type="primary" @click="deleteClassesMore" >批量删除</el-button > </div> </div> </template> <h1 style="font-size: 35px; text-align: center">申报项目列表</h1> <el-table :data="classesTable" v-model:selection="selectedRows" @selection-change="handleSelectionChange" style="width: 100%" border highlight :row-class-name="tableRowClassName" :header-cell-style="{ textAlign: 'center' }" :cell-style="{ textAlign: 'center' }" > <el-table-column width="100" type="selection"> </el-table-column> <el-table-column label="序号" width="80" type="index"> </el-table-column> <el-table-column label="项目编号" width="90" prop="id"> </el-table-column> <el-table-column label="项目名称" prop="projectName"></el-table-column> <el-table-column label="申报开始日期" prop="startDate"> </el-table-column> <el-table-column label="申报结束日期" prop="endDate"></el-table-column> <el-table-column label="申报状态" prop="status"> <template #default="scope"> <p v-if="scope.row.status == 0">已申报</p> <p v-else-if="scope.row.status == 1">审核中</p> <p v-else>已审核</p> </template> </el-table-column> <el-table-column label="操作" width="120"> <template #default="{ row }"> <el-button v-show="row.status === 1 || row.status == 0" circle plain @click="showDialog(row)" text primary color="#626aef" >审核</el-button > <el-button :icon="Delete" circle plain type="danger" @click="deleteClasses(row)" ></el-button> </template> </el-table-column> <template #empty> <el-empty description="没有数据" /> </template> </el-table> <!-- 分页条 --> <el-pagination layout="->, prev, pager, next,jumper,total, sizes" v-model:current-page="pageNum" v-model:page-size="pageSize" :page-sizes="[5, 10, 15, 20, 30, 50, 100]" background page-count:pageTotle :total="total" @size-change="onSizeChange" @current-change="onCurrentChange" style="margin-top: 20px; justify-content: center" /> <!-- <div style="margin-top: 20px">选中的行:{{ selectedRows }}</div> --> <!-- 添加弹窗 --> <el-dialog v-model="dialogVisible" :title="title" width="30%"> <el-form :model="classesModel" label-width="auto" label-position="left" style="padding-right: 30px" ref="formRef" > <el-form-item label="项目编号" prop="id" v-show="title == '申报项目信息'" > <el-input v-model="classesModel.id" disabled></el-input> </el-form-item> <el-form-item label="项目名称" prop="projectName" :rules="{ required: true, message: '不能为空', trigger: 'blur', }" > <el-input v-model="classesModel.projectName" minlength="1" maxlength="100" clearable ></el-input> </el-form-item> <el-form-item label="申报开始时间" prop="startDate" v-show="title == '申报项目信息'" :rules="{ required: true, message: '不能为空', trigger: 'blur', }" > <el-input v-model="classesModel.startDate" clearable></el-input> </el-form-item> <el-form-item label="申报结束时间" prop="endDate" v-show="title == '申报项目信息'" :rules="{ required: true, message: '不能为空', trigger: 'blur', }" > <el-input v-model="classesModel.endDate" clearable></el-input> </el-form-item> <el-form-item label="申报开始时间" prop="startDate" v-show="title == '添加申报'" :rules="{ required: true, message: '不能为空', trigger: 'blur', }" > <el-date-picker v-model="classesModel.startDate" type="date" placeholder="申报开始时间" /> </el-form-item> <el-form-item label="申报结束时间" prop="endDate" v-show="title == '添加申报'" :rules="{ required: true, message: '不能为空', trigger: 'blur', }" > <el-date-picker v-model="classesModel.endDate" type="date" placeholder="申报结束时间" /> </el-form-item> <el-form-item label="申报状态" prop="status" :rules="{ required: true, message: '不能为空', trigger: 'blur', }" > <el-select v-model="classesModel.status" placeholder="申报状态" clearable style="width: 130px; padding: 0px 20px" > <el-option label="已申报" value="0" v-show="classesModel.status == '已申报'" /> <el-option v-show="title == '添加申报'" label="已申报" value="0" /> <el-option label="审核中" value="1" /> <el-option label="已审核" value="2" /> </el-select> </el-form-item> </el-form> <template #footer> <span class="dialog-footer"> <el-button @click="dialogVisible = false">返回</el-button> <el-button type="primary" @click="title == '添加申报' ? addclasses() : updateClasses()" >{{ title == "添加申报" ? "确定" : "审核" }} </el-button> </span> </template> </el-dialog> </el-card> </template> <style lang="scss" > .page-container { min-height: 100%; box-sizing: border-box; .header { display: flex; align-items: center; justify-content: space-between; } } .el-table .even-row { background-color: #f09191; } .el-table .odd-row { background-color: #ffffff; } .demo-date-picker { display: flex; width: 100%; padding: 0; flex-wrap: wrap; } .demo-date-picker .block { padding: 30px 0; text-align: center; border-right: solid 1px var(--el-border-color); flex: 1; } .demo-date-picker .block:last-child { border-right: none; } .demo-date-picker .demonstration { display: block; color: var(--el-text-color-secondary); font-size: 14px; margin-bottom: 20px; } </style>
(4)LogOff.vue:
<script setup> import { ref } from "vue"; import { userLogoffService } from "@/api/user.js"; import { ElMessage, ElMessageBox } from "element-plus"; import { useRouter } from "vue-router"; const router = useRouter(); import { useUserInfoStore } from "@/stores/userInfo.js"; const userInfoStore = useUserInfoStore(); import { useTokenStore } from "@/stores/token.js"; const tokenStore = useTokenStore(); const data = ref({ username: "", password: "", }); const logoff = () => { ElMessageBox.confirm("确定注销用户吗?", "警告", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning", }) .then(async () => { if (data.value.username != userInfoStore.info.username) { return ElMessage.error("用户名或密码不正确"); } else { let result = await userLogoffService(data.value); console.log(result); if (result.data) { router.push("/login"); tokenStore.removeToken(); userInfoStore.removeInfo(); return ElMessage.success("注销成功"); } ElMessage.error("用户名或密码不正确"); } }) .catch(() => { ElMessage({ type: "info", message: "已取消", }); }); }; </script> <template> <el-card class="page-container"> <template #header> <div class="header"> <span>账户注销</span> </div> </template> <el-row> <el-col :span="12"> <el-form :model="data" label-width="100px" size="large"> <el-form-item label="登录名称"> <el-input v-model="data.username"></el-input> </el-form-item> <el-form-item label="用户密码" prop="password"> <el-input v-model="data.password"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="logoff()">提交</el-button> </el-form-item> </el-form> </el-col> </el-row> </el-card> </template>
(5)UserAvatar.vue:
<script setup> import { ref } from "vue"; import avatar from "@/assets/default.png"; import { Plus, Upload } from "@element-plus/icons-vue"; const uploadRef = ref(); //读取用户信息 import { useUserInfoStore } from "@/stores/userInfo.js"; const userInfoStore = useUserInfoStore(); const imgUrl = ref(userInfoStore.info.userpic); //读取token信息 import { useTokenStore } from "@/stores/token.js"; const tokenStore = useTokenStore(); //图片上传成功的回调 const uploadSuccess = (result) => { //回显图片 imgUrl.value = result.data; }; //调用接口,更新头像url import { userAvatarUpdateService } from "@/api/user.js"; import { ElMessage } from "element-plus"; const updateAvatar = async () => { let result = await userAvatarUpdateService(imgUrl.value); ElMessage.success(result.message ? result.message : "修改成功"); console.log(userInfoStore.info); //更新pinia中的数据 userInfoStore.info.userpic = imgUrl.value; }; </script> <template> <el-card> <template #header> <div class="header"> <span>更换头像</span> </div> </template> <el-row> <el-col :span="12"> <el-upload ref="uploadRef" class="avatar-uploader" :show-file-list="false" :auto-upload="true" action="/api/upload" name="file" :headers="{ cookieshopUser: tokenStore.token }" :on-success="uploadSuccess" > <img v-if="imgUrl" :src="imgUrl" class="avatar" /> <img v-else :src="avatar" width="278" /> </el-upload> <br /> <el-button type="primary" :icon="Plus" size="large" @click="uploadRef.$el.querySelector('input').click()" > 选择图片 </el-button> <el-button type="success" :icon="Upload" size="large" @click="updateAvatar" > 上传头像 </el-button> </el-col> </el-row> </el-card> </template> <style lang="scss" scoped> .avatar-uploader { :deep() { .avatar { width: 278px; height: 278px; display: block; } .el-upload { border: 1px dashed var(--el-border-color); border-radius: 6px; cursor: pointer; position: relative; overflow: hidden; transition: var(--el-transition-duration-fast); } .el-upload:hover { border-color: var(--el-color-primary); } .el-icon.avatar-uploader-icon { font-size: 28px; color: #8c939d; width: 278px; height: 278px; text-align: center; } } } </style>
(6)UserInfo.vue:
<script setup> import { ref } from "vue"; import { useUserInfoStore } from "@/stores/userInfo.js"; const userInfoStore = useUserInfoStore(); const userInfo = ref({ ...userInfoStore.info, }); // import { useClassesInfoStore } from "@/stores/classes.js"; // const classesInfoStore = useClassesInfoStore(); // const classesInfo = ref({ // ...classesInfoStore.info, // }); const rules = { nickname: [ { required: true, message: "请输入用户昵称", trigger: "blur" }, { pattern: /^\S{2,10}$/, message: "昵称必须是2-10位的非空字符串", trigger: "blur", }, ], email: [ { required: true, message: "请输入用户邮箱", trigger: "blur" }, { type: "email", message: "邮箱格式不正确", trigger: "blur" }, ], }; //修改用户信息 import { userInfoUpdateService } from "@/api/user.js"; import { ElMessage } from "element-plus"; const updateUserInfo = async () => { let result = await userInfoUpdateService(userInfo.value); ElMessage.success(result.message ? result.message : "修改成功"); //更新pinia中的数据 userInfoStore.info.name = userInfo.value.name; userInfoStore.info.email = userInfo.value.email; userInfoStore.info.phone = userInfo.value.phone; userInfoStore.info.address = userInfo.value.address; }; </script> <template> <el-card class="page-container"> <template #header> <div class="header"> <span>基本资料</span> </div> </template> <el-row> <el-col :span="12"> <el-form :model="userInfo" :rules="rules" label-width="100px" size="large" > <el-form-item label="登录名称"> <el-input v-model="userInfo.username" disabled></el-input> </el-form-item> <el-form-item label="用户昵称" prop="name"> <el-input v-model="userInfo.name"></el-input> </el-form-item> <el-form-item label="用户邮箱" prop="email"> <el-input v-model="userInfo.email"></el-input> </el-form-item> <el-form-item label="用户电话" prop="phone"> <el-input v-model="userInfo.phone"></el-input> </el-form-item> <el-form-item label="用户地址" prop="address"> <el-input v-model="userInfo.address"></el-input> </el-form-item> <el-form-item> <el-button type="primary" @click="updateUserInfo" >提交修改</el-button > </el-form-item> </el-form> </el-col> </el-row> </el-card> </template>
(7)UserResetPassword.vue:
<script setup> import { reactive, ref } from "vue"; import { ElMessage } from "element-plus"; import { CheckUserPwdGetService, userPwdUpdateService } from "@/api/user.js"; import { useUserInfoStore } from "@/stores/userInfo.js"; const userInfoStore = useUserInfoStore(); const userInfo = ref({ ...userInfoStore.info }); const ruleForm = reactive({ originPwd: "", newPwd: "", oncePwd: "", }); const checkOncePwd = (rule, value, callback) => { if (value === "") { callback(new Error("再次输入密码不能为空")); } else if (value !== ruleForm.newPwd) { callback(new Error("两次输入密码不一致")); } else { callback(); } }; const ruleFormRef = ref(); const rules = reactive({ originPwd: [{ required: true, message: "原密码不能为空", trigger: "blur" }], newPwd: [{ required: true, message: "新密码不能为空", trigger: "blur" }], oncePwd: [ { required: true, message: "再次输入密码不能为空", trigger: "blur" }, { validator: checkOncePwd, trigger: "blur" }, ], }); const submitForm = () => { const formEl = ruleFormRef.value; if (!formEl) return; formEl.validate(async (valid) => { if (valid) { let data = { id: userInfo.value.id, password: ruleForm.originPwd, }; try { let result = await CheckUserPwdGetService(data); if (result.data) { let data1 = { id: userInfo.value.id, password: ruleForm.newPwd, }; let result1 = await userPwdUpdateService(data1); formEl.resetFields(); ElMessage.success(result1.data); } else { ElMessage.error("原密码错误"); } } catch (error) { console.error("密码验证出错:", error); ElMessage.error("密码验证出错,请稍后再试"); } console.log("submit!"); } else { console.log("error submit!"); } }); }; const resetForm = () => { const formEl = ruleFormRef.value; if (!formEl) return; formEl.resetFields(); }; </script> <template> <el-card> <template #header> <span>重置密码</span> </template> <div style="text-algin: center; padding: 10px 10px"> <el-form ref="ruleFormRef" style="max-width: 600px" :model="ruleForm" status-icon :rules="rules" label-width="auto" class="demo-ruleForm" > <el-form-item label="原密码" prop="originPwd"> <el-input v-model="ruleForm.originPwd" type="password" autocomplete="off" /> </el-form-item> <el-form-item label="新密码" prop="newPwd"> <el-input v-model="ruleForm.newPwd" type="password" autocomplete="off" /> </el-form-item> <el-form-item label="再次输入密码" prop="oncePwd"> <el-input v-model="ruleForm.oncePwd" type="password" autocomplete="off" /> </el-form-item> <el-form-item> <div> <el-button type="primary" @click="submitForm"> 提交 </el-button> <el-button @click="resetForm">重置</el-button> </div> </el-form-item> </el-form> </div> </el-card> </template>
12.在router包下修改index.js
import { createRouter, createWebHistory } from 'vue-router' import LoginVue from "@/views/Login.vue"; import LayoutVue from "@/views/Layout.vue"; import UserInfoVue from "@/views/user/UserInfo.vue"; import UserAvatarVue from "@/views/user/UserAvatar.vue"; import UserResetPasswordVue from "@/views/user/UserResetPassword.vue"; import LogOff from "@/views/user/LogOff.vue"; import AllVue from "@/views/project/All.vue"; const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: [ { path: '/', name: 'LayoutVue', component: LayoutVue, redirect: "/all", children: [ { path: "/all", component: AllVue, }, { path: "/user/info", component: UserInfoVue, }, { path: "/user/repwd", component: UserResetPasswordVue, }, { path: "/user/avatar", component: UserAvatarVue, }, { path: "/user/logoff", component: LogOff, }, ], }, { path: "/login", name: "login", component: LoginVue, }, ] }) export default router
13.前端最终目录。
四、代码地址:
1.前端:projectinf_qian: vue3+elementplus+pinia+token统一拦截
2.后端:projectinf_hou: springboot3+mybatisplus+redis+JWt+统一拦截器token