小工具知识网

当前位置: 首页 > 行业知识 > 从零搭建SpringBoot3+Vue3前后端分离项目基座,中小项目可用

行业知识

从零搭建SpringBoot3+Vue3前后端分离项目基座,中小项目可用

2026-11-08 07:25:43 来源:互联网转载

文章目录

  • 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- 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驱动包com.mysql mysql-connector-j较之前版本有所改变

                      4.0.0            org.springframework.boot        spring-boot-starter-parent        3.2.4                 com.buzhisuoyun    base_manage    0.0.1-SNAPSHOT    base_manage    base_manage            17                            org.springframework.boot            spring-boot-starter-web                            com.mysql            mysql-connector-j            runtime                            org.springframework.boot            spring-boot-starter-test            test                                                    org.springframework.boot                spring-boot-maven-plugin                        

                  1.4 MySql环境整合,使用druid连接池

                  添加 jdbc依赖,防止项目启动时找不到Bean报错

                     org.springframework.boot   spring-boot-starter-jdbc

                  添加druid依赖

                      com.alibaba    druid-spring-boot-3-starter    1.2.20

                  项目使用yml格式配置文件,修改resources下application.properties

                  为application.yml, 并删除文件内容

                  配置项目端口号server:  port: 8099

                  配置数据源和druid 连接池

                  spring:  # 数据源  datasource:    type: com.alibaba.druid.pool.DruidDataSource    driver-class-name: com.mysql.cj.jdbc.Driver    url: jdbc:mysql://127.0.0.1:3306/base_manage?serverTimezone=UTC    username: root    password: root

                  启动项目, 查看日志,端口和druid 初始化, 整合成功

                  1.5 整合mybatis-plus

                  1.5.1 引入mybatis-plus

                  官网地址: https://baomidou.com/

                  1、添加依赖

                      com.baomidou    mybatis-plus-spring-boot3-starter    3.5.5

                  2、配置

                  # mybatis-plusmybatis-plus:  configuration:    # sql日志, 开发调试时开启    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  global-config:    db-config:      id-type: auto

                  启动项目,出现mybatis-plus日志

                  1.5.2 配置代码生成器

                  1.引入相关依赖

                       com.baomidou     mybatis-plus-generator     3.5.5    org.springframework.boot    spring-boot-starter-freemarker 
                  1. 新建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 {    /**     * 添加分页插件     */    @Bean    public MybatisPlusInterceptor mybatisPlusInterceptor() {        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));//如果配置多个插件,切记分页最后添加        //interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); 如果有多数据源可以不配具体类型 否则都建议配上具体的DbType        return interceptor;    }}

                    @MapperScan(“com/buzhisuoyun/base_manage/mapper”) 扫描mapper路劲,也可在启动类配置

                    1.6 整合swagger3(knife4j)

                    1.6.1 整合

                    引入依赖

                        com.github.xiaoymin    knife4j-openapi3-jakarta-spring-boot-starter    4.5.0

                    配置:

                    # springdoc-openapi 配置springdoc:  swagger-ui:    path: /swagger-ui.html    tags-sorter: alpha    operations-sorter: alpha  api-docs:    path: /v3/api-docs  group-configs:    - group: 'default'      paths-to-match: '/**'      packages-to-scan: com.buzhisuoyun.base_manage# knife4j 配置knife4j:  # 是否启用增强  enable: true  # 开启生产环境屏蔽  production: false  # 是否认证登录  basic:    # basic是否开启,默认为false    enable: true    username: knife4j    password: knife4j  setting:    language: zh_cn    enable-version: true    enable-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;@Configurationpublic class Knife4jConfig {    @Bean    public 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 {    @Autowired    private UserService userService;/**     * 用户列表分页     * @param pageSize 每页显示的条数     * @param currentPage  要查询的页     * @param name  用户姓名     * @return  Result     */    @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 pageList(@RequestParam int pageSize, @RequestParam int currentPage, @Nullable @RequestParam String name) {        IPage page = userService.pageList(pageSize, currentPage, name);        if (page == null) {            return Result.error("查询失败");        }        //PageResultBean pageResultBean = new PageResultBean(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 {    @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  Result 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);    }    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;    }    @Override    public 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 {    @Schema(description = "数据总条数")    private Long total;    @Schema(description = "当前页数据集合")    private List items;    public PageResultBean() {    }    public PageResultBean(Long total, List items) {        this.total = total;        this.items = items;    }    public static  PageResultBean getInstance(Long total, List items) {        return new PageResultBean(total, items);    }    public Long getTotal() {        return total;    }    public void setTotal(Long total) {        this.total = total;    }    public List getItems() {        return items;    }    public void setItems(List 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;// 全局异常处理@RestControllerAdvicepublic class GlobalExceptionHandler {    private final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);    @ExceptionHandler(Exception.class)    public Result handlerException(Exception e) {        logger.warn(e.getMessage());        return Result.error(StringUtils.hasLength(e.getMessage()) ? e.getMessage() : "操作失败");    }}

                    1.9 整合JWT,生成token

                    引入依赖

                           com.auth0     java-jwt     4.4.0 

                    封装工具类, 放到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 claims) {        return JWT.create()                .withClaim("claims", claims)                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 60 * 60))       // 失效时间1小时                .sign(Algorithm.HMAC256(KEY));    }    // 接收token,验证并返回数据    public static Map 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 THREAD_LOCAL = new ThreadLocal();    // 获取存储值    public static  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 > 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;@Componentpublic class LoginInterceptor implements HandlerInterceptor {    @Override    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {        // 获取token        String token = request.getHeader("Authorization");        // 验证token        try {            Map claims = JwtUtil.parseToken(token);            // 存储业务信息到线程变量            ThreadLocalUtil.set(claims);            // 放行拦截            return true;        } catch (Exception e) {            // 登录信息异常或未登录,http响应状态码为401            response.setStatus(401);            // 拦截请求            return false;        }    }    @Override    public 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;@Configurationpublic class WebConfig implements WebMvcConfigurer {    @Autowired    private LoginInterceptor loginInterceptor;    @Override    public void addInterceptors(InterceptorRegistry registry) {        // 配置放行资源        // 无需拦截的接口集合        List 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 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 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 getCurrentUser() {        Map 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文件不需要的东西,最后如下所示

                        

                    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) => {        // 请求前回调        // 添加token        const tokenStore = useTokenStore()        // 判断有无token        if (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-routerimport {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.jsimport {createPersistedState} from'pinia-persistedstate-plugin’ const persist = createPersistedState()pinia.use(persist)
                                  • 定义状态Store时指定持久化配置参数

                                  • 在src/stores/下定义token.js和userInfo.js 用来存储token和用户相关信息

                                    token.js// 定义 storeimport { 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

                                        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) 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("校验失败")        }    })}//定义函数,清空数据模型的数据const clearRegisterData = ()=>{    registerData.value={        loginName: '',        password:'',        rePassword:''    }}                                                                                

                                        登录

                                        记住我 登录 /* 样式 */.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; } }}

                                        2.8.3 布局页面

                                        • Layout .vue
                                          import LeftLayout from './LeftLayout.vue'import Header from './Header.vue'import MainView from './MainView.vue'import {ref} from 'vue'const isCollapse = ref(false)const parentClick = (isCollapseValue) => {    isCollapse.value = isCollapseValue;    console.log(isCollapse.value)}                                                  后台 ©2024 Created by buzhisuoyun                .el-footer {        display: flex;        align-items: center;        justify-content: center;        font-size: 14px;        color: #666;        height: 38px;        padding: 0;        background-color: #FFFFFF;     }
                                          • LeftLayout.vue
                                                                                      后台管理                                                            API管理                      API列表            item two            item three                      item four            item one                                              Navigator Two                                    Navigator Three                                    Navigator Four                                                  用户管理                      用户列表            个人信息            修改密码                    import {  Document,  Menu as IconMenu,  Location,  Share,  UserFilled,  Setting,} from '@element-plus/icons-vue'import {ref, defineProps} from 'vue'type Props = {    isCollapse: boolean}defineProps()    .el-menu-vertical-demo {        height: 100vh;    }    .el-menu-item {        min-width: 0;    }    .containerp {          /* 你可以设置容器的样式,例如宽度、高度、背景色等 */          /* width: 300px; /* 示例宽度 */          height: 48px;                  padding: 10px; /* 内边距 */          border-bottom: 2px solid; /* 设置下边框宽度和样式 */          border-bottom-color: #F5F5F5; /* 设置下边框颜色为红色 */      }        .image {          display: inline-block;          vertical-align: middle; /* 图片与文字垂直居中对齐 */          margin-right: 6px; /* 图片右边距,可选 */          width: 20px;    }          .text {          display: inline-block;          vertical-align: middle; /* 文字与图片垂直居中对齐 */          font-weight: bold; /* 加粗文字 */          font-size: 14px;    }
                                            • Heard.vue
                                                                                                                                                                                                  首页                                        {{ item.meta.title }}                                                                                                    账号:{{userData.loginName}}                                         {{userData.name}}                                                                                                                                     个人信息                                                                修改密码                                                                退出登录                                                                                            import {        Fold,        Expand,        MoreFilled,        EditPen,        ArrowLeft,        UserFilled    } from '@element-plus/icons-vue'    import {ref, defineEmits, watch} from 'vue'    // 面包屑    import { useRouter,useRoute } from 'vue-router'    let router = useRouter()    let route = useRoute()    let breadList = ref()    let getMatched=()=>{        console.log("route.matched:",route.matched);        console        breadList.value = route.matched.filter(item => item.meta && item.meta.title);    }    getMatched()        watch(() => route.path, (newValue, oldValue) => { //监听路由路径是否发生变化,之后更改面包屑        console.log("======")        breadList.value = route.matched.filter(item =>             item.meta && item.meta.title    );        console.log("breadList.value", breadList.value)    })    import useUserInfoStore from '@/stores/userInfo.js'    const userInfoStore = useUserInfoStore();    // 用户数据模型    let userData = ref({        id: '',        name: '',        loginName: ''    })    import {currentUserService} from '@/api/user.js'    // 获取登录用户信息    const getUser = async () => {        let result = await currentUserService()        // console.log(result)        userData.value = result.data;        userInfoStore.setInfo(result.data)        // console.log("userData:",userData)    }    getUser()    // 折叠按钮处理    const emits = defineEmits(["parentClick"])    const isMenuOpen = ref(false)    const toggleCollapse = () => {        isMenuOpen.value = !isMenuOpen.value        console.log(isMenuOpen.value)        emits("parentClick", isMenuOpen.value)    }    .container {          overflow: auto; /* 清除浮动影响 */          height: 48px;        padding: 10px; /* 内边距 */          border-bottom: 2px solid; /* 设置下边框宽度和样式 */          border-bottom-color: #F5F5F5; /* 设置下边框颜色为红色 */          background-color: #FFFFFF;       }          .left {          height: 48px;        float: left;           display: flex;        align-items: center; /* 垂直居中子项 */          justify-content: center; /* 水平居中子项(如果需要)*/     }      .left > p {          padding-right: 10px; /* 设置直接子元素的 padding */      }        .right {          float: right;         display: flex;         align-items: center; /* 垂直居中子项 */          justify-content: center; /* 水平居中子项(如果需要)*/       }    .right > p {          padding-right: 10px; /* 设置直接子元素的 padding */      }
                                              • MainView.vue
                                                                                                        .app-main{width:100%;height:100%;        background-color: #FFFFFF; }

                                                项目结构:

                                                3.项目代码

                                                https://download.csdn.net/download/qq_51355375/89085020

上一篇:服务器延时响应怎么关闭?

下一篇:揭秘易语言准星源码,一探究竟,洞悉核心原理