文章目录
- 1. 后端项目搭建
-
- 1.1 环境准备
- 1.2 数据表准备
- 1.3 SpringBoot3项目创建
- 1.4 MySql环境整合,使用druid连接池
- 1.5 整合mybatis-plus
-
- 1.5.1 引入mybatis-plus
- 1.5.2 配置代码生成器
- 1.5.3 配置分页插件
- 1.6 整合swagger3(knife4j)
-
- 1.6.1 整合
- 1.6.2 使用
- 1.7 数据交互处理
-
- 1.7.1 响应数据封装(公共返回数据类)
- 1.7.2 分页查询返回数据封装
- 1.8 全局异常处理
- 1.9 整合JWT,生成token
- 1.10 封装ThreadLocal 工具类
- 1.11 MD5封装
- 1.12登录验证拦截
- 1.13 登录和获取当前用户信息接口处理
- 2. 前端项目搭建
-
- 2.1 环境准备
- 2.2 创建Vue3项目
- 2.3 项目搭建准备
- 2.4 Element Plus 安装使用
- 2.5 axios 安装使用
-
- 2.5.1 安装
- 2.5.2 配置(创建实例,配置请求、响应拦截器)
- 2.5.3 配置跨域
- 2.6 Vue Router 安装使用
- 2.7 Pinia状态管理库
- 2.7.1 Pinia持久化插件-persist
- 2.8 搭建管理页面基础框架
-
- 2.8.1 在src下创建api目录,次目录存放请求http方法的封装,创建user.js, 里边写封装请求方法
- 2.8.2 登录页面
- 2.8.3 布局页面
- 3.项目代码
项目使用SpringBoot3+Vue3, 后端使用springboot3, mybatisPlus, druid,knife4j(swagger3),Jwt; 前端 vue3 , element-plus, axios, pinia, vue-router; 项目前后端分离, 可持续扩展, 代码放到最后
1. 后端项目搭建
1.1 环境准备
- spring-boot3 最低支持jdk17, 所以需要准备jdk17环境
- Idea 版本IntelliJ IDEA 2021.2及以后,版本关系参考[https://blog.csdn.net/m0_62258564/article/details/134527268](https://blog.csdn.net/m0_62258564/article/details/134527268)
- maven 版本参考以上链接
- MySql8
1.2 数据表准备
创建数据库 base_manage, 并创建表
CREATE TABLE user(id INT NOT NULL AUTO_INCREMENT COMMENT '主键',login_name VARCHAR(255) NOT NULL COMMENT '登录名(账号)',password VARCHAR(255) NOT NULL COMMENT '密码',name varchar(50) NOT NULL COMMENT '姓名',sex char(1) NOT NULL COMMENT '性别',phone VARCHAR(20) COMMENT '联系电话',PRIMARY KEY(id))COMMENT '用户信息表';
用户表只是便于后续过程搭建操作,可根据需求修改
1.3 SpringBoot3项目创建
springboot版本可根据需求选择,这里选则默认的3.2.4
耐心等待项目构建完成, 构建完成pom文件如下,请注意,mysql驱动包<groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId>
较之前版本有所改变
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>3.2.4</version><relativePath/> <!-- lookup parent from repository --></parent><groupId>com.buzhisuoyun</groupId><artifactId>base_manage</artifactId><version>0.0.1-SNAPSHOT</version><name>base_manage</name><description>base_manage</description><properties><java.version>17</java.version></properties><dependencies><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.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies><build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build></project>
1.4 MySql环境整合,使用druid连接池
添加 jdbc依赖,防止项目启动时找不到Bean报错
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
添加druid依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-3-starter -->
<dependency><groupId>com.alibaba</groupId><artifactId>druid-spring-boot-3-starter</artifactId><version>1.2.20</version>
</dependency>
项目使用yml格式配置文件,修改resources下application.properties
为application.yml, 并删除文件内容
配置项目端口号
server:port: 8099
配置数据源和druid 连接池
spring:# 数据源datasource:type: com.alibaba.druid.pool.DruidDataSourcedriver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://127.0.0.1:3306/base_manage?serverTimezone=UTCusername: rootpassword: root
启动项目, 查看日志,端口和druid 初始化, 整合成功
1.5 整合mybatis-plus
1.5.1 引入mybatis-plus
官网地址: https://baomidou.com/
1、添加依赖
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-spring-boot3-starter -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.5</version>
</dependency>
2、配置
# mybatis-plus
mybatis-plus:configuration:# sql日志, 开发调试时开启log-impl: org.apache.ibatis.logging.stdout.StdOutImplglobal-config:db-config:id-type: auto
启动项目,出现mybatis-plus日志
1.5.2 配置代码生成器
1.引入相关依赖
<!-- mybatis-plus 代码生成器-->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-generator</artifactId><version>3.5.5</version>
</dependency><!-- mybatis-plus代码生成器模板引擎 -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-freemarker</artifactId></dependency>
-
新建utis包,并在下边创建MybatisPlusGenerator.java代码生成器配置类
package com.buzhisuoyun.base_manage.utils;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.OutputFile;
import com.baomidou.mybatisplus.generator.config.rules.DbColumnType;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;import java.io.File;
import java.sql.Types;
import java.util.Collections;public class MybatisPlusGenerator {
static final String url = “jdbc:mysql://127.0.0.1:3306/base_manage?serverTimezone=UTC”; // 数据库地址
static final String username = “root”; // 数据库用户名
static final String password = “root”; // 数据库密码
static final String authorName = “buzhisuoyun”; // 作者名
static final String parentPackageNameResource = “com/buzhisuoyun/base_manage”; // mapper.xml路径
static final String parentPackageNameJava = “com.buzhisuoyun.base_manage”; // java 文件父包名
// 要生成代码对应的数据表名
static final String tableName = “user”;public static void main(String[] args) {FastAutoGenerator.create(url, username, password)// 1.全局配置.globalConfig(builder -> {builder.author(authorName) // 设置作者.enableSpringdoc() // 开启 swagger 模式// 获取当前工程路径并定位到项目java目录下.outputDir(System.getProperty("user.dir") + "/src/main/java"); // 指定输出目录})// 2.数据源配置.dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> {int typeCode = metaInfo.getJdbcType().TYPE_CODE;if (typeCode == Types.SMALLINT) {// 自定义类型转换return DbColumnType.INTEGER;}return typeRegistry.getColumnType(metaInfo);}))// 3.包名策略配置.packageConfig(builder -> {builder.parent(parentPackageNameJava) // 设置父包名.entity("entity").mapper("mapper").service("service").serviceImpl("service.impl").controller("controller")//.moduleName("system") // 设置父包模块名.pathInfo(Collections.singletonMap(OutputFile.xml, System.getProperty("user.dir") + "/src/main/resources/" + parentPackageNameResource + "/mapper")); // 设置mapperXml生成路径})// 策略配置.strategyConfig(builder -> {builder.addInclude(tableName) // 设置需要生成的表名// 覆盖已生成文件.entityBuilder().enableFileOverride().mapperBuilder().enableFileOverride().serviceBuilder().enableFileOverride().formatServiceFileName("%sService");//.addTablePrefix("t_", "c_"); // 设置过滤表前缀})// 配置模板.templateConfig(builder -> {//builder.controller(""); // 不生成controller}).templateEngine(new FreemarkerTemplateEngine()) // 使用Freemarker引擎模板,默认的是Velocity引擎模板.execute(); }
}
- 修改数据库相关信息
- 修改authorName
- 修改parentPackageNameResource, parentPackageNameJava
- 修改tableName, 要生成代码的数据表名称,多个表使用,分割
- .enableSpringdoc() 可以选择,生成swagger3文档注释
修改完成后运行main函数,生成相应代码,mapper.xml在resource下与java同路径下
实体类import io.swagger.v3.oas.annotations.media.Schema;报错,是因为swagger3依赖还未导入,下边整合swagger3后就不会报错了
1.5.3 配置分页插件
新建config包,并在下边创建配置类MybatisPlusConfig.java
package com.buzhisuoyun.base_manage.config;import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
@MapperScan("com/buzhisuoyun/base_manage/mapper")
public class MybatisPlusConfig {/*** 添加分页插件*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加//interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbTypereturn interceptor;}
}
@MapperScan(“com/buzhisuoyun/base_manage/mapper”) 扫描mapper路劲,也可在启动类配置
1.6 整合swagger3(knife4j)
1.6.1 整合
引入依赖
<!-- API 文档 knife4j -->
<!-- https://mvnrepository.com/artifact/com.github.xiaoymin/knife4j-openapi3-jakarta-spring-boot-starter -->
<dependency><groupId>com.github.xiaoymin</groupId><artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId><version>4.5.0</version>
</dependency>
配置:
# springdoc-openapi 配置
springdoc:swagger-ui:path: /swagger-ui.htmltags-sorter: alphaoperations-sorter: alphaapi-docs:path: /v3/api-docsgroup-configs:- group: 'default'paths-to-match: '/**'packages-to-scan: com.buzhisuoyun.base_manage# knife4j 配置
knife4j:# 是否启用增强enable: true# 开启生产环境屏蔽production: false# 是否认证登录basic:# basic是否开启,默认为falseenable: trueusername: knife4jpassword: knife4jsetting:language: zh_cnenable-version: trueenable-swagger-models: true
在config下创建配置类
package com.buzhisuoyun.base_manage.config;import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class Knife4jConfig {@Beanpublic OpenAPI springShopOpenApi() {return new OpenAPI()// 接口文档标题.info(new Info().title("接口文档").description("api接口文档").version("1.0版本"));}
}
配置完后,启动项目,访问路径http://localhost:9999/doc.html,用户名密码就是配置文件中的,结果如图
1.6.2 使用
实体类使用@Schema 注解,在mybatis-plus选择的话会自动生成
Conntroller 层使用:
@Tag(name = "用户接口")
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate UserService userService;
/*** 用户列表分页* @param pageSize 每页显示的条数* @param currentPage 要查询的页* @param name 用户姓名* @return Result<PageResultBean<List<User>>>*/@GetMapping("/pageList")@Operation(summary = "用户列表分页查询")@Parameters({@Parameter(name = "Authorization", in = ParameterIn.HEADER, required = true, description = "token"),@Parameter(name = "pageSize", required = true, description = "每页显示的条数"),@Parameter(name = "currentPage", required = true, description = "要查询的页"),@Parameter(name = "name", description = "用户姓名", required = false)})public Result<PageResultBean<User>> pageList(@RequestParam int pageSize, @RequestParam int currentPage, @Nullable @RequestParam String name) {IPage<User> page = userService.pageList(pageSize, currentPage, name);if (page == null) {return Result.error("查询失败");}//PageResultBean<User> pageResultBean = new PageResultBean<User>(page.getTotal(), page.getRecords());return Result.success(PageResultBean.getInstance(page.getTotal(), page.getRecords()));}
}
1.7 数据交互处理
1.7.1 响应数据封装(公共返回数据类)
封装返回数据封装类,放到common包下:
package com.buzhisuoyun.base_manage.common;import io.swagger.v3.oas.annotations.media.Schema;public class Result<T> {@Schema(description = "业务状态码 0:成功 1: 失败")private int code; // 业务状态码 0:成功 1: 失败@Schema(description = "提示信息")private String message; // 提示信息@Schema(description = "返回数据")private T data; // 响应数据public Result(int code, String message, T data) {this.code = code;this.message = message;this.data = data;}// 操作成功返回响应结果(带响应数据)public static <E> Result<E> success(E data) {return new Result<>(0, "操作成功", data);}public static <E> Result<E> success() {return new Result<>(0, "操作成功", null);}public static <E> Result<E> error(String message) {return new Result<>(1, message, null);}public int getCode() {return code;}public void setCode(int code) {this.code = code;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public T getData() {return data;}public void setData(T data) {this.data = data;}@Overridepublic String toString() {return "Result{" +"code=" + code +", message='" + message + ''' +", data=" + data +'}';}
}
1.7.2 分页查询返回数据封装
封装分页查询数据,放到common包下:
package com.buzhisuoyun.base_manage.common;import io.swagger.v3.oas.annotations.media.Schema;import java.util.List;public class PageResultBean<T> {@Schema(description = "数据总条数")private Long total;@Schema(description = "当前页数据集合")private List<T> items;public PageResultBean() {}public PageResultBean(Long total, List<T> items) {this.total = total;this.items = items;}public static <E> PageResultBean<E> getInstance(Long total, List<E> items) {return new PageResultBean<>(total, items);}public Long getTotal() {return total;}public void setTotal(Long total) {this.total = total;}public List<T> getItems() {return items;}public void setItems(List<T> items) {this.items = items;}
}
1.8 全局异常处理
创建xception包,在包下放全局异常处理类GlobalExceptionHandler.java,类名放置 位置可以随意,为便于分类整理, 同类包放到一个包下
使用springboot @RestControllerAdvice 注解配置
package com.buzhisuoyun.base_manage.exception;import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;// 全局异常处理
@RestControllerAdvice
public class GlobalExceptionHandler {private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);@ExceptionHandler(Exception.class)public Result<String> handlerException(Exception e) {logger.warn(e.getMessage());return Result.error(StringUtils.hasLength(e.getMessage()) ? e.getMessage() : "操作失败");}
}
1.9 整合JWT,生成token
引入依赖
<!-- jwt --><dependency><groupId>com.auth0</groupId><artifactId>java-jwt</artifactId><version>4.4.0</version></dependency>
封装工具类, 放到utils包下:
package com.buzhisuoyun.base_manage.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 = "buzhisuoyun"; // 密钥// 接收数据,生成token并返回public static String getToken(Map<String, Object> claims) {return JWT.create().withClaim("claims", claims).withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 失效时间1小时.sign(Algorithm.HMAC256(KEY));}// 接收token,验证并返回数据public static Map<String, Object> parseToken(String token) {return JWT.require(Algorithm.HMAC256(KEY)).build().verify(token).getClaim("claims").asMap();}
}
1.10 封装ThreadLocal 工具类
因项目使用前后端分离,使用ThreadLocal 线程变量存储用户登录信息,替代session
package com.buzhisuoyun.base_manage.utils;public class ThreadLocalUtil {// 提供ThreadLocal 对象private static final ThreadLocal<Object> THREAD_LOCAL = new ThreadLocal<>();// 获取存储值public static <T> T get() {return (T) THREAD_LOCAL.get();}// 存储值public static void set(Object value) {THREAD_LOCAL.set(value);}// 清除THREAD_LOCAL 防止内存泄漏public static void remove() {THREAD_LOCAL.remove();}
}
1.11 MD5封装
package com.buzhisuoyun.base_manage.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);}// 测试public static void main(String[] args) {System.out.println(Md5Util.getMD5String("admin"));}}
1.12登录验证拦截
1、创建包interceptors,在包下配置登录拦截器
package com.buzhisuoyun.base_manage.interceptors;import com.buzhisuoyun.base_manage.utils.JwtUtil;
import com.buzhisuoyun.base_manage.utils.ThreadLocalUtil;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;import java.util.Map;@Component
public class LoginInterceptor implements HandlerInterceptor {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {// 获取tokenString token = request.getHeader("Authorization");// 验证tokentry {Map<String, Object> claims = JwtUtil.parseToken(token);// 存储业务信息到线程变量ThreadLocalUtil.set(claims);// 放行拦截return true;} catch (Exception e) {// 登录信息异常或未登录,http响应状态码为401response.setStatus(401);// 拦截请求return false;}}@Overridepublic void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {// 清除ThreadLocal业务数据ThreadLocalUtil.remove();}
}
2、在config包下创建WebConfig配置类,注册登录拦截器
package com.buzhisuoyun.base_manage.config;import com.buzhisuoyun.base_manage.interceptors.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;import java.util.ArrayList;
import java.util.List;@Configuration
public class WebConfig implements WebMvcConfigurer {@Autowiredprivate LoginInterceptor loginInterceptor;@Overridepublic void addInterceptors(InterceptorRegistry registry) {// 配置放行资源// 无需拦截的接口集合List<String> ignorePath = new ArrayList<>();// knife4j(swagger)ignorePath.add("/swagger-resources/**");ignorePath.add("/doc.html");ignorePath.add("/v3/**");ignorePath.add("/webjars/**");ignorePath.add("/static/**");ignorePath.add("/templates/**");ignorePath.add("/error");// 登录页面ignorePath.add("/user/login");registry.addInterceptor(loginInterceptor).excludePathPatterns(ignorePath);}
}
1.13 登录和获取当前用户信息接口处理
@PostMapping("/login")@Operation(summary = "用户登录")@Parameters({@Parameter(name = "loginName", description = "登录名", required = true, schema = @Schema(type = "sting")),@Parameter(name = "password", description = "密码", required = true, schema = @Schema(type = "sting"))})public Result<String> login(@RequestBody User user) {if (user == null || "".equals(user.getLoginName()) || user.getLoginName() == null || "".equals(user.getPassword()) || user.getPassword() == null) {return Result.error("用户名密码不能为空");}// 检验用户名是否存在User eruser = userService.findByLoginName(user.getLoginName());if (eruser == null) {return Result.error("用户名不存在");}// 检验用户密码是否正确if (Md5Util.getMD5String(user.getPassword()).equals(eruser.getPassword())) {Map<String, Object> claims = new HashMap<>();claims.put("id", eruser.getId());claims.put("name", eruser.getName());claims.put("loginName", eruser.getLoginName());String token = JwtUtil.getToken(claims);return Result.success(token);}return Result.error("密码错误");}/*** 获取当前登录用户信息* @return User*/@GetMapping("/currentUser")@Operation(summary = "获取当前登录用户信息")@Parameter(name = "Authorization", in = ParameterIn.HEADER, required = true, description = "token")public Result<User> getCurrentUser() {Map<String, Object> userSession = ThreadLocalUtil.get();int id = (int) userSession.get("id");User user = userService.getUserById(id);if (user != null) {return Result.success(user);}return Result.error("用户状态异常");}
因为是做简单后端项目搭建,所以token存在一定问题,如在修改用户密码后必须强制重新登录或采用redis缓存token,同时验证浏览器token和用户token,或采用第三方认证中心解决等
项目工程结构如图
2. 前端项目搭建
2.1 环境准备
- node安装
- vscode安装
2.2 创建Vue3项目
在将要存放vue3项目的路径打开cmd, 使用以下命令创建项目
npm init vue@latest
此时项目创建完成,vscode打开项目目录,在资源目录空白右键,打开终端
执行命令 npm install
安装依赖,等待安装完成后执行 npm run dev
运行项目
访问路径可访问项目
在终端ctrl c 可停止运行项目
项目描述如图,图引自黑马开源教程PPT
2.3 项目搭建准备
说明:项目中使用组合式API
删掉components下所有文件,删除App.vue文件不需要的东西,最后如下所示
<script setup></script><template><router-view></router-view>
</template><style scoped></style>
2.4 Element Plus 安装使用
-
安装
npm install element-plus --save
-
引入:在main.js中引入Element Plus(参照官方文档)https://element.eleme.cn/#/zh-CN/component/quickstart
import { createApp } from ‘vue’
import ElementPlus from ‘element-plus’
import ‘element-plus/dist/index.css’
import App from ‘./App.vue’const app = createApp(App)
app.use(ElementPlus)
app.mount(‘#app’) -
使用: 访问Element官方文档,复制组件代码,修改调整
2.5 axios 安装使用
2.5.1 安装
npm install axios
2.5.2 配置(创建实例,配置请求、响应拦截器)
在src目录下新建 utils, 并在utils 下创建request.js 进行axios配置
// 请求配置import axios from "axios";// 定义公共前缀,创建请求实例
// const baseUrl = "http://localhost:8080";
const baseURL = '/api/';
const instance = axios.create({baseURL})import { ElMessage } from "element-plus"
import { useTokenStore } from "@/stores/token.js"
// 配置请求拦截器
instance.interceptors.request.use((config) => {// 请求前回调// 添加tokenconst tokenStore = useTokenStore()// 判断有无tokenif (tokenStore.token) {config.headers.Authorization = tokenStore.token}return config},(err) => {// 请求错误的回调Promise.reject(err)}
)import router from "@/router";
// 添加响应拦截器
instance.interceptors.response.use(result => {// 判断业务状态码if (result.data.code === 0) {return result.data;}// 操作失败ElMessage.error(result.data.message ? result.data.message : '服务异常')// 异步操作的状态转换为失败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
2.5.3 配置跨域
在vite.config.js 中defineConfig配置代理,实现跨域
server: {proxy: {'/api': { // 获取路径中包含了/api的请求target: 'http://localhost:9999', // 服务端地址changeOrigin: true, // 修改源rewrite:(path) => path.replace(/^/api/, '') // api 替换为 ''}}}
2.6 Vue Router 安装使用
-
安装
npm install vue-router@4
-
在src/router/index.js中创建路由器,并导出
// 导入vue-router
import {createRouter, createWebHistory} from ‘vue-router’// 导入组件
import LoginVue from ‘@/views/Login.vue’
import LayoutVue from ‘@/views/Layout.vue’
import UserList from ‘@/views/user/UserList.vue’
import EditPassword from ‘@/views/user/EditPassword.vue’
import DisplayUser from ‘@/views/user/DisplayUser.vue’// 定义路由关系
const routes = [
{path: ‘/login’, component: LoginVue},
{
path: ‘/’, component: LayoutVue, redirect: ‘’, children: [
{path: ‘/user/userlist’, name: “/user/userlist”, component: UserList, meta: {
title: “用户列表”
},},
{path: ‘/user/editpassword’, name: “/user/editpassword”, component: EditPassword, meta: {
title: “修改密码”
}
},
{path: ‘/user/displayuser’, name: “/user/displayuser”, component: DisplayUser, meta: {
title: “个人信息”
}}
]
}
]// 创建路由器
const router = createRouter({
history: createWebHistory(),
routes: routes
})export default router
-
在vue应用实例中使用vue-router
在main.js 中
import router from ‘@/router’
app.use(router) -
声明router-view标签,展示组件内容
在app.vue 中
2.7 Pinia状态管理库
- 安装
npm install pinia
2.7.1 Pinia持久化插件-persist
-
安装persist npm install pinia-persistedstate-plugin
-
在pinia中使用persist
main.js
import {createPersistedState} from’pinia-persistedstate-plugin’ const persist = createPersistedState()
pinia.use(persist) -
定义状态Store时指定持久化配置参数
-
在src/stores/下定义token.js和userInfo.js 用来存储token和用户相关信息
token.js
// 定义 store
import { defineStore } from “pinia”
import {ref} from ‘vue’
/*
第一个参数:名字,唯一性
第二个参数:函数,函数的内部可以定义状态的所有内容返回值: 函数
*/
export const useTokenStore = defineStore(‘token’, () => {
// 响应式变量
const token = ref(‘’)// 修改token值函数 const setToken = (newToken) => {token.value = newToken }// 移除token值函数 const removeToke = () => {token.value = '' }return {token, setToken, removeToke }
},
{
persist: true // 持久化存储
}
)userInfo.js
import { defineStore } from “pinia”
import {ref} from ‘vue’const useUserInfoStore = defineStore(‘userInfo’, () => {
const info = ref({})const setInfo = (newInfo) => {info.value = newInfo }const removeInfo = () => {info.value = {} }return {info, setInfo, removeInfo}
},
{
persist: true
}
)export default useUserInfoStore;
-
在组件中使用store 示例
import {userLoginService} from ‘@/api/user.js’
import {useTokenStore} from ‘@/stores/token.js’
import {useRouter} from ‘vue-router’
const router = useRouter()
const tokenStore = useTokenStore();
const login = async ()=>{
// 校验表单
if (!ruleFormRef.value) return
console.log(“校验”)
await ruleFormRef.value.validate(async (valid) => {
if (valid) {
console.log(“校验成功”)
// 调用接口,完成登录
let result = await userLoginService(registerData.value);
/* if(result.code===0){
alert(result.msg? result.msg : ‘登录成功’)
}else{
alert(‘登录失败’)
} */
//alert(result.msg? result.msg : ‘登录成功’)
ElMessage.success(result.msg ? result.msg : ‘登录成功’)
//token存储到pinia中
tokenStore.setToken(result.data)
//跳转到首页 路由完成跳转
router.push(‘/’)
} else {
console.log(“校验失败”)
}
})
}// 配置请求拦截器
instance.interceptors.request.use(
(config) => {
// 请求前回调
// 添加token
const tokenStore = useTokenStore()
// 判断有无token
if (tokenStore.token) {
config.headers.Authorization = tokenStore.token
}
return config
},
(err) => {
// 请求错误的回调
Promise.reject(err)
}
)
2.8 搭建管理页面基础框架
2.8.1 在src下创建api目录,次目录存放请求http方法的封装,创建user.js, 里边写封装请求方法
import request from "@/utils/request.js"// 登录接口调用函数
export const userLoginService = (loginData) => {return request.post('/user/login', loginData)
}// 获取当前登录用户信息
export const currentUserService = () => {return request.get('/user/currentUser')
}// 获取所有用户信息
export const allUserService = () => {return request.get('/user/userList')
}// 分页查询
export const pageListService = (pageParam) => {return request.get('/user/pageList', {params: pageParam})
}// 新增用户
export const addUserService = (addData) => {return request.post('/user/add', addData)
}// 根据id获取用户信息
export const getUserById = (id) => {return request.get('/user/getuserById', {params: id})
}// 修改用户信息
export const updateUserService = (data) => {return request.put('/user/update', data)
}// 删除用户
export const deleteByIdService = (id) => {console.log("deleteRequestid:", id)return request.delete('/user/delete/' + id)
}
2.8.2 登录页面
- 安装
npm install sass -D
在src下创建views目录,用于存放vue页面组件
登录 Login.vue
<script setup>
import { User, Lock } from '@element-plus/icons-vue'
import { ref, reactive } from 'vue'
import { ElMessage } from 'element-plus'
//定义数据模型
const registerData = ref({loginName: 'admin',password:'admin',rePassword: ''
})// 定义表单组件的引用
const ruleFormRef = ref(null)//定义表单校验规则
const rules = ref({loginName: [{ required: true, message: '请输入用户名', trigger: 'blur' },{ min: 5, max: 16, message: '长度为5~16位非空字符', trigger: 'blur' }],password: [{ required: true, message: '请输入密码', trigger: 'blur' },{ min: 5, max: 16, 2: '长度为5~16位非空字符', trigger: 'blur' }]
})//绑定数据,复用注册表单的数据模型
//表单数据校验
//登录函数
import {userLoginService} from '@/api/user.js'
import {useTokenStore} from '@/stores/token.js'
import {useRouter} from 'vue-router'
const router = useRouter()
const tokenStore = useTokenStore();
const login = async ()=>{// 校验表单if (!ruleFormRef.value) returnconsole.log("校验")await ruleFormRef.value.validate(async (valid) => {if (valid) {console.log("校验成功")// 调用接口,完成登录let result = await userLoginService(registerData.value);/* if(result.code===0){alert(result.msg? result.msg : '登录成功')}else{alert('登录失败')} *///alert(result.msg? result.msg : '登录成功')ElMessage.success(result.msg ? result.msg : '登录成功')//token存储到pinia中tokenStore.setToken(result.data)//跳转到首页 路由完成跳转router.push('/')} else {console.log("校验失败")}})
}//定义函数,清空数据模型的数据
const clearRegisterData = ()=>{registerData.value={loginName: '',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="ruleFormRef" :model=registerData size="large" autocomplete="off" :rules="rules"><el-form-item><h1>登录</h1></el-form-item><el-form-item prop="loginName"><el-input :prefix-icon="User" placeholder="请输入用户名" v-model="registerData.loginName"></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></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>
2.8.3 布局页面
-
Layout .vue
项目结构:
3.项目代码
https://download.csdn.net/download/qq_51355375/89085020
此项目不包含动态路由,菜单权限部分, 如需要可参考 : https://blog.csdn.net/qq_51355375/article/details/139722876