SpringBoot+vue 全栈开发

前提

本教程主要是面向全栈开发的快速上手教程,具备Java及前端基础即可学习!

主要内容

开发工具:IDEA+VSCode
后端:SpringBoot+MyBatisPlus
前端:Vue+VueRouter+Vuex+ElementUI
接口调试:Swagger
数据模拟:MockJS
身份认证:JWT
后台管理:vue-admin-template
项目部署:阿里云ECS+Nginx+MySQL

快速搭建一个springboot项目

springboot默认整合了Tomcat
创建项目的方式有如下两种方法

方法1:idea选择Spring initializr方式创建

1、idea选择Spring initializr,点击next
在这里插入图片描述
:选择Spring initializr表示创建Springboot项目,springboot项目是包含了Maven项目的内容的,即含有如下内容结构:
在这里插入图片描述
2、按如下配置参数,配置完后点击next
在这里插入图片描述
3、这里选择web,然后选择Spring web,这样就会有springmvc的相关依赖。这里一定要选择大版本是2的,如果选择3,那么你的jdk必须要是17以上,SpringBoot3以上版本不支持JDK11和JDK8,支持的最低版本是JDK17
点击next
在这里插入图片描述
4、最后可以修改项目名也可以不修改,点击【finish】,完成Springboot项目的创建:
在这里插入图片描述

方法2:导入文件夹,然后自己创建需要的内容

我用的是这种方式,上一个方法我在创建后存依赖下载不了的问题。
1、文件结构如下:
在这里插入图片描述
2、pom.xml

<?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">
    <!-- pom模型版本 -->
    <modelVersion>4.0.0</modelVersion>
    <!-- 父级项目 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.3</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <!-- 项目信息 -->
    <groupId>com.lxj</groupId><!-- 项目唯一标识 -->
    <artifactId>Springboot-test</artifactId><!-- 项目名 -->
    <version>0.0.1-SNAPSHOT</version><!-- 版本 -->
    <name>Springboot-test</name><!-- 项目名 -->
    <description>Demo project for Spring Boot</description>
    <!-- 属性设置 -->
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </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>

2、启动类

package com.xj;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class StartUpApplication {
    public static void main(String[] args) {
        SpringApplication.run(StartUpApplication.class, args);
    }
}

简单验证Springboot项目

1、新建一个controller包,再新建一个类
在这里插入图片描述
代码如下:

package com.xj.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    // http://localhost:8080/hello
    // http:// 协议
    // localhost:8080 域名
    // /hello 路径
    @GetMapping("/hello")
    public String hello(){
        return "hello SpringBoot";
    }
}

2、启动Springboot的启动类
在这里插入图片描述
3、Springboot的内置的Tomcat默认的协议是http,默认的启动端口是8080
在这里插入图片描述
4、浏览器访问:http://localhost:8080/hello,如下出现controller类,“/hello”路径下的方法的返回值,则表示你的Springboot项目搭建没有问题。
在这里插入图片描述

Springboot的热部署

1、添加依赖

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-devtools</artifactId>
   <optional>true</optional>
</dependency>

2、Springboot的配置文件中添加配置
配置文件位置:
在这里插入图片描述
添加的代码如下:

#热部署
spring.devtools.restart.enabled=true
spring.devtools.restart.additional-paths=src/main/java    

3、idea的settings的如下位置,勾选,然后点击应用
在这里插入图片描述
4、在代码编辑页面,ctrl+shft+alt+/ ,点击第一个
在这里插入图片描述
进入后,勾选如下图中选项即可,勾选后点击【close】
在这里插入图片描述
这样就完成了热部署的配置,之后修改代码后:ctrl+s 即可,不用在重启

Springboot Controller

@RestController

controller 控制器
在这里插入图片描述
Spring Boot提供了@Controller和@RestController两种注解来标识此类负责接受和处理http请求。如果请求需要返回的是页面和数据,使用@controller,如果只是返回数据则可以使用@RestController
我们现在主要是前后端分离,@Controller需要返回一个视图,@RestController返回的可以是JSON也可以是字符串,所以RestController使用的比较多

@RequestMapping

@GetMapping(“/hello”) 这个写法等价于:@RequestMapping(value = “/hello”,method = RequestMethod.GET)
@RequestMapping(“/hello”) //表示可以接受任何请求,只要是访问这个地址就可以接受到
@RequestMapping(value = “/hello”,method = RequestMethod.GET) //表示只能接受get请求,浏览器地址栏输入的都是get请求

package com.xj.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class HelloController {
    // http://localhost:8080/hello
    // http:// 协议
    // localhost:8080 域名
    // /hello 路径
//    @GetMapping("/hello") 这个写法等价于:@RequestMapping(value = "/hello",method = RequestMethod.GET)
//    @RequestMapping("/hello") //表示可以接受任何请求,只要是访问这个地址就可以接受到
    @RequestMapping(value = "/hello",method = RequestMethod.GET) //表示只能接受get请求,浏览器地址栏输入的都是get请求
    public String hello(){
        return "hello SpringBoot";
    }
}

以下展示了@RequestMapping的传参等用法
1、get方式传参
方法中接受的参数和浏览器地址中传递的参数名一致,则直接传递即可,如果不一致,则需要做特殊的处理才可以传递。
代码如下:

package com.xj.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ParaController {
    /**
    * 这是最简单的
    * */
    @RequestMapping(value = "/getTest1",method = RequestMethod.GET)
    public String getTest1(){
        return "getTest1";
    }

    /**
     * controller接受参数,方法中的参数和浏览器传过来的参数一致
     * */
    @RequestMapping(value = "/getTest2",method = RequestMethod.GET)
    // http://localhost:8023/getTest2?nickname=zhangsan&phone=123
    // 方法中的参数必须和浏览器传过来的参数名字要保持一致,这样才可以被方法接受到
    public String getTest2(String nickname,String phone){
        System.out.println("nickname:"+nickname);
        System.out.println("phone:"+phone);
        return "getTest2";
    }

    /**
     * controller接受参数,方法中的参数名和浏览器传过来的不一致,必须加@RequestParam(value = "nickname")
     * 这里浏览器如果不传的话就会报400的错
     * 如果不传,但是不想它报错后面加:required = false
     * */
    @RequestMapping(value = "/getTest3",method = RequestMethod.GET)
    // http://localhost:8023/getTest3?nickname=zhangsan
    public String getTest3(@RequestParam(value = "nickname",required = false) String name){
        System.out.println("name:"+name);
        return "getTest3";
    }
}

这里如果注释后面不加 required = false 的话,浏览器不传递 nickename = zhangsan,访问这个方法会报错,报错如下:
在这里插入图片描述
通常来说4开头的错误都是客户端的问题。

2、post方式传参
浏览器无法直接输入地址访问post请求,需要借助一个工具调试,这里用的是比较广泛的工具postman
post请求传参一般是在body中传,使用的传参方式如下:
在这里插入图片描述
也可以在param中传,这样的话是会拼接到地址后面,参数少可以这样,参数多不建议在这里传
在这里插入图片描述

具体代码如下:

package com.xj.controller;

import com.xj.entity.User;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class ParaControllerPost {
    @RequestMapping(value = "/postTest1", method = RequestMethod.POST)
    public String postTest1() {
        return "POST1请求";
    }

    @RequestMapping(value = "/postTest2", method = RequestMethod.POST)
    public String postTest2(String username, String password) {
        System.out.println("username:" + username);
        System.out.println("password:" + password);
        return "POST2请求";
    }

    @RequestMapping(value = "/postTest3", method = RequestMethod.POST)
    public String postTest3(User user) {
        System.out.println(user);
        return "POST3请求";
    }

    @RequestMapping(value = "/postTest4", method = RequestMethod.POST)
    public String postTest4(@RequestBody User user) {
        System.out.println(user);
        return "POST4请求";
    }
}

user实体

package com.xj.entity;

public class User {
    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

请求3中,我们只需要传入和User对应的属性名相同的参数,就会自动封装到User对象中,如下:
在这里插入图片描述
这里的参数和User类中的属性名相同:
在这里插入图片描述
后台获取到传过来的参数,并封装到User对象中
在这里插入图片描述
请求4中@RequestBody表示只接受json方式传参,如果还是用之前的传参方式访问请求4,就会报如下错误:
在这里插入图片描述

前台的传参必须是json,并且参数的数据类型需要和后台对应,应如下传参:
在这里插入图片描述

Springboot 文件上传+拦截器

静态文件目录

static这个目录Springboot默认做了一个映射,前台可以直接访问,如下即可说明:
在这里插入图片描述
前台访问效果:
在这里插入图片描述
如果你需要在static文件夹中新建一个目录,浏览器访问的时候则需要再加一个目录结构

Springboot文件上传

方法中有MultipartFile类接收前端传过来的文件,用如下这种方式发送请求:
在这里插入图片描述
具体代码如下:

package com.xj.controller;

import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;

@RestController
public class fileUploadController {
    @PostMapping("/upload") //等价于@RequestMapping(value = "/upload",method = RequestMethod.POST)
    public String uploadFile(String nickname, MultipartFile photo, HttpServletRequest request){
        System.out.println(nickname);
        //获取图片的原始名称
        System.out.println(photo.getOriginalFilename());
        //获取文件类型
        System.out.println(photo.getContentType());
        //文件上传后暂时存放路径
        String path = request.getServletContext().getRealPath("/upload");
        System.out.println(path);
        return "上传成功";
    }
}

Springboot拦截器

前端访问地址,先会经过拦截器,拦截器返回true则可以继续访问Controller
具体代码如下

package com.xj.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // request 可以拿到前端传过来的一些数据,比如cookie,response可以返回一些信息给前端
        System.out.println("loginInterceptor"); //这里可以代表做一些操作
        return true; //返回true,表示通过拦截器,false表示不通过拦截器,不会继续往下访问Controller
    }
}

拦截器要注册和限定拦截请求路径

package com.xj.config;

import com.xj.interceptor.LoginInterceptor;
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 InterceptorConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //注册拦截器,拦截的地址是 /user下面的
//        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/user/**");
        //注册拦截器,拦截的地址是所有
        registry.addInterceptor(new LoginInterceptor());
    }
}

访问之前的post3请求,先会打印拦截器中的内容,请求如下:
在这里插入图片描述
结果:

loginInterceptor
username:zhangsan
password:123

RestFul服务+Swagger

Springboot中的restful

restful 是一种编程规范和约束
如下是Springboot体现的restful编程规范

package com.xj.controller;

import com.xj.entity.User;
import org.springframework.web.bind.annotation.*;

@RestController
public class UserController {
    @GetMapping("/user/{id}")
    public String getUserById(@PathVariable int id) { //@PathVariable 注解配合 @GetMapping("/user/{id}") 中的{id}可以在请求路径中传递参数值并获取到参数值
        System.out.println(id);
        return "根据id获取用户信息";
    }

    @PostMapping("/user")
    public String save(User user) {
        return "添加用户";
    }

    @PutMapping("/user")
    public String update(User user) {
        return "跟新用户";
    }

    @DeleteMapping("/user/{id}")
    public String deleteById(@PathVariable int id) {
        System.out.println(id);
        return "根据ID删除用户";
    }
}

postman验证,需要选择不同的请求方式
第一个验证根据用户id获取用户信息
在这里插入图片描述
第二个验证,添加用户
在这里插入图片描述
第三个验证,更新用户
在这里插入图片描述
第四个验证:删除用户
在这里插入图片描述

Springboot整合swagger

swagger主要是用来监控接口的,swagger可以检测到后台提供给前台的接口,并且可以在监控页面中调试
1、配置类
com包下的所有api都交给swagger管理,配置如下

package com.xj.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;

@Configuration
@EnableSwagger2
public class SwaggerConfig {
    @Bean
    public Docket createRestApi() {
        return new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(apiInfo())
                .select()
                .apis(RequestHandlerSelectors.basePackage("com")) //com包下的所有api都交给swagger管理
                .paths(PathSelectors.any())
                .build();
    }

    /**
     * 创建该API的基本信息(这些基本信息会展现在文档页面中)
     * 访问地址:http://项目实际地址/swagger-ui.html
     *
     * @return
     */
    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
                .title("Spring Boot中使用Swagger2")
                .description("")
                .version("1.0")
                .build();
    }

}

2、访问地址:http://项目实际地址/swagger-ui.html
在这里插入图片描述
这里有描述是因为在Controller中添加了 @ApiOperation(value = “根据id获取用户信息”)
3、可以在swagger进行调试
在这里插入图片描述

注意:
在添加swagger配置类后,本人的Springboot在启动的时候报错,报错信息如下:

org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

完整报错:

2023-09-03 18:19:48.806  WARN 8548 --- [  restartedMain] ConfigServletWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
2023-09-03 18:19:49.042  INFO 8548 --- [  restartedMain] o.apache.catalina.core.StandardService   : Stopping service [Tomcat]
2023-09-03 18:19:49.057  INFO 8548 --- [  restartedMain] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2023-09-03 18:19:49.080 ERROR 8548 --- [  restartedMain] o.s.boot.SpringApplication               : Application run failed

org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException
	at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:181) ~[spring-context-5.3.22.jar:5.3.22]
	at org.springframework.context.support.DefaultLifecycleProcessor.access$200(DefaultLifecycleProcessor.java:54) ~[spring-context-5.3.22.jar:5.3.22]
	at org.springframework.context.support.DefaultLifecycleProcessor$LifecycleGroup.start(DefaultLifecycleProcessor.java:356) ~[spring-context-5.3.22.jar:5.3.22]
	at java.lang.Iterable.forEach(Iterable.java:75) ~[na:1.8.0_152]
	at org.springframework.context.support.DefaultLifecycleProcessor.startBeans(DefaultLifecycleProcessor.java:155) ~[spring-context-5.3.22.jar:5.3.22]
	at org.springframework.context.support.DefaultLifecycleProcessor.onRefresh(DefaultLifecycleProcessor.java:123) ~[spring-context-5.3.22.jar:5.3.22]
	at org.springframework.context.support.AbstractApplicationContext.finishRefresh(AbstractApplicationContext.java:935) ~[spring-context-5.3.22.jar:5.3.22]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:586) ~[spring-context-5.3.22.jar:5.3.22]
	at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:147) ~[spring-boot-2.7.3.jar:2.7.3]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) [spring-boot-2.7.3.jar:2.7.3]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:408) [spring-boot-2.7.3.jar:2.7.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:308) [spring-boot-2.7.3.jar:2.7.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) [spring-boot-2.7.3.jar:2.7.3]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) [spring-boot-2.7.3.jar:2.7.3]
	at com.xj.StartUpApplication.main(StartUpApplication.java:9) [classes/:na]
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_152]
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_152]
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_152]
	at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_152]
	at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) [spring-boot-devtools-2.7.3.jar:2.7.3]
Caused by: java.lang.NullPointerException: null
	at springfox.documentation.spi.service.contexts.Orderings$8.compare(Orderings.java:112) ~[springfox-spi-2.9.2.jar:null]
	at springfox.documentation.spi.service.contexts.Orderings$8.compare(Orderings.java:109) ~[springfox-spi-2.9.2.jar:null]
	at com.google.common.collect.ComparatorOrdering.compare(ComparatorOrdering.java:37) ~[guava-20.0.jar:na]
	at java.util.TimSort.countRunAndMakeAscending(TimSort.java:355) ~[na:1.8.0_152]
	at java.util.TimSort.sort(TimSort.java:220) ~[na:1.8.0_152]
	at java.util.Arrays.sort(Arrays.java:1438) ~[na:1.8.0_152]
	at com.google.common.collect.Ordering.sortedCopy(Ordering.java:855) ~[guava-20.0.jar:na]
	at springfox.documentation.spring.web.plugins.WebMvcRequestHandlerProvider.requestHandlers(WebMvcRequestHandlerProvider.java:57) ~[springfox-spring-web-2.9.2.jar:null]
	at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2.apply(DocumentationPluginsBootstrapper.java:138) ~[springfox-spring-web-2.9.2.jar:null]
	at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper$2.apply(DocumentationPluginsBootstrapper.java:135) ~[springfox-spring-web-2.9.2.jar:null]
	at com.google.common.collect.Iterators$7.transform(Iterators.java:750) ~[guava-20.0.jar:na]
	at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:47) ~[guava-20.0.jar:na]
	at com.google.common.collect.TransformedIterator.next(TransformedIterator.java:47) ~[guava-20.0.jar:na]
	at com.google.common.collect.MultitransformedIterator.hasNext(MultitransformedIterator.java:52) ~[guava-20.0.jar:na]
	at com.google.common.collect.MultitransformedIterator.hasNext(MultitransformedIterator.java:50) ~[guava-20.0.jar:na]
	at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:249) ~[guava-20.0.jar:na]
	at com.google.common.collect.ImmutableList.copyOf(ImmutableList.java:209) ~[guava-20.0.jar:na]
	at com.google.common.collect.FluentIterable.toList(FluentIterable.java:614) ~[guava-20.0.jar:na]
	at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.defaultContextBuilder(DocumentationPluginsBootstrapper.java:111) ~[springfox-spring-web-2.9.2.jar:null]
	at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.buildContext(DocumentationPluginsBootstrapper.java:96) ~[springfox-spring-web-2.9.2.jar:null]
	at springfox.documentation.spring.web.plugins.DocumentationPluginsBootstrapper.start(DocumentationPluginsBootstrapper.java:167) ~[springfox-spring-web-2.9.2.jar:null]
	at org.springframework.context.support.DefaultLifecycleProcessor.doStart(DefaultLifecycleProcessor.java:178) ~[spring-context-5.3.22.jar:5.3.22]
	... 19 common frames omitted

后查阅得出报错原因是因为本人使用的Springboot版本是2.7.3(Springboot 2.6.x以上和swagger2不兼容),之后我是修改Springboot版本号解决的(修改成了2.1.12.RELEASE),还有一种解决方法是在Springboot的启动文件中添加相关配置(本人没有去尝试),具体可以查看这边博客 https://blog.csdn.net/weixin_48568302/article/details/126163107

MyBatisPlus

mybatis + Springboot

mybatis以前叫ibatis
Springboot整合mybatis
1、Springboot的配置文件中添加如下配置:

#Springboot + MybatisPlus + alibaba连接池 连接mysql数据库 配置
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/ruoyi?userSSL=false
spring.datasource.username=root
spring.datasource.password=123456
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

mybatis和mybatisplus整合Springboot的Springboot配置文件是一样配置的
2、Springboot的启动类中添加如下注解:

@MapperScan("com.xj.mapper")

这个注解告诉Springboot扫描mapper的路径
3、mapper接口

package com.xj.mapper;

import com.xj.entity.Role;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface RoleMapper {
    //查询所有角色
    @Select("select role_name as roleName,role_key as roleKey from sys_role")
    public List<Role> queryAllRole();
}

这里的sql是写在注解中的,还有一种方式是用xml来写sql
查询sql中用as是因为这样可以和实体中的属性名称保持一致
4、实体:

package com.xj.entity;

public class Role {
    private String roleName;
    private String roleKey;

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleKey() {
        return roleKey;
    }

    public void setRoleKey(String roleKey) {
        this.roleKey = roleKey;
    }

    @Override
    public String toString() {
        return "Role{" +
                "roleName='" + roleName + '\'' +
                ", roleKey='" + roleKey + '\'' +
                '}';
    }
}

5、controller 直接调用mapper中的查询方法

package com.xj.controller;

import com.xj.entity.Role;
import com.xj.mapper.RoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class RoleController {
    @Autowired
    private RoleMapper roleMapper;

    @GetMapping("/role")
    public List queryRole(){
        List<Role> list = roleMapper.queryAllRole();
        return list;
    }
}

mybatisplus + springboot

1、Springboot配置文件,和整合mybatis的一样
2、Springboot的启动类添加扫描注解,和整合mybatis的一样
3、mapper接口,不用写接口方法,继承baseMapper即可

package com.xj.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xj.entity.Role;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface RoleMapper extends BaseMapper<Role> {
}

4、实体,如果表名和实体名不一致,需要用注解写明数据库表名如 @TableName(“sys_role”),如果表中属性名和实体属性名不一致,也需要用注解写名数据库的属性名 @TableField(“role_key”),具体如下:

package com.xj.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;

import java.util.List;

@TableName("sys_role")
public class Role {
    @TableField("role_id")
    private  String roleId;
    @TableField("role_name")
    private String roleName;
    @TableField("role_key")
    private String roleKey;
    //exist = false加这个表示数据库没有查到对于字段也不会报错
    @TableField(exist = false)
    List<RoleMenu> roleMenus;

    public List<RoleMenu> getRoleMenus() {
        return roleMenus;
    }

    public void setRoleMenus(List<RoleMenu> roleMenus) {
        this.roleMenus = roleMenus;
    }

    public String getRoleId() {
        return roleId;
    }

    public void setRoleId(String roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleKey() {
        return roleKey;
    }

    public void setRoleKey(String roleKey) {
        this.roleKey = roleKey;
    }

    @Override
    public String toString() {
        return "Role{" +
                "roleId='" + roleId + '\'' +
                ", roleName='" + roleName + '\'' +
                ", roleKey='" + roleKey + '\'' +
                '}';
    }
}


5、Controller直接调用mapper中的方法,因为其继承了BaseMapper,所以直接调用BaseMapper中的操作数据库方法即可(mybatisplus只支持单表操作不用去手写sql,对于多表查询还是要依赖于mybatis并且需要写sql)。

package com.xj.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xj.entity.Role;
import com.xj.mapper.RoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class RoleController {
    @Autowired
    private RoleMapper roleMapper;

    @GetMapping("/role")
    public List queryRole(){
        List<Role> list = roleMapper.selectList(null);
        return list;
    }

    @GetMapping("/role/find")
    public List queryRoleByRoleName(){
        QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_key","admin");
        List<Role> roles = roleMapper.selectList(queryWrapper);
        return roles;
    }

    @GetMapping("/roleMenus")
    public List queryRoleMenus(){
        List<Role> list = roleMapper.selectAllRoleAndRoleMenu();
        return list;
    }

   @GetMapping("/role/findByPage")
    public IPage findRoleByPage(){ 
        //设置起始值和每页数据条数
        Page<Role> page = new Page<>(0,5);
        IPage iPage = roleMapper.selectPage(page,null);
        return iPage;
    }
}


此处调用的查询方法selectList我们不需要传值,所以传个null即可。

mybatis和mybatisplus可以一起使用

因为mybatisplus提供了单表的crud方法,但是多表的不支持,所以可以用mybatis的Mapper类,写我们自己想要的多表查询sql
查询角色下有多少菜单,这里用到了多表查询,使用的是mybatis
1、实体
实体RoleMenu,角色和菜单的关系表,相应数据如下:
在这里插入图片描述

代码如下:

package com.xj.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;

@TableName("sys_role_menu")
public class RoleMenu {
    @TableField("role_id")
    private String roleId;
    @TableField("menu_id")
    private String menuId;

    public String getRoleId() {
        return roleId;
    }

    public void setRoleId(String roleId) {
        this.roleId = roleId;
    }

    public String getMenuId() {
        return menuId;
    }

    public void setMenuId(String menuId) {
        this.menuId = menuId;
    }
}

实体Role,数据库数据如下:
在这里插入图片描述

代码如下,这里主要添加了 List roleMenus; 用来接收菜单列表:

package com.xj.entity;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;

import java.util.List;

@TableName("sys_role")
public class Role {
    @TableField("role_id")
    private  String roleId;
    @TableField("role_name")
    private String roleName;
    @TableField("role_key")
    private String roleKey;
    //exist = false加这个表示数据库没有查到对于字段也不会报错
    @TableField(exist = false)
    List<RoleMenu> roleMenus;

    public List<RoleMenu> getRoleMenus() {
        return roleMenus;
    }

    public void setRoleMenus(List<RoleMenu> roleMenus) {
        this.roleMenus = roleMenus;
    }

    public String getRoleId() {
        return roleId;
    }

    public void setRoleId(String roleId) {
        this.roleId = roleId;
    }

    public String getRoleName() {
        return roleName;
    }

    public void setRoleName(String roleName) {
        this.roleName = roleName;
    }

    public String getRoleKey() {
        return roleKey;
    }

    public void setRoleKey(String roleKey) {
        this.roleKey = roleKey;
    }

    @Override
    public String toString() {
        return "Role{" +
                "roleId='" + roleId + '\'' +
                ", roleName='" + roleName + '\'' +
                ", roleKey='" + roleKey + '\'' +
                '}';
    }
}

2、Mapper
RoleMenuMapper

package com.xj.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xj.entity.RoleMenu;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface RoleMenuMapper extends BaseMapper<RoleMenu> {
    @Select("select * from sys_role_menu where role_id = #{roleId}")
    List<RoleMenu> selectRoleMenuByRoleId(int roleId);
}

RoleMapper

package com.xj.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.xj.entity.Role;
import org.apache.ibatis.annotations.*;

import java.util.List;

@Mapper
public interface RoleMapper extends BaseMapper<Role> {
    @Select("select * from sys_role")
    @Results(
            value = {
                    //column 数据库表字段,property 实体字段
                    @Result(column = "role_id",property = "roleId"),
                    @Result(column = "role_name",property = "roleName"),
                    @Result(column = "role_key",property = "roleKey"),
                    //这里 我们调用了 RoleMenuMapper.selectRoleMenuByRoleId 的方法,@Many 一对多的意思
                    @Result(column = "role_id",property = "roleMenus",javaType = List.class,
                    many = @Many(select = "com.xj.mapper.RoleMenuMapper.selectRoleMenuByRoleId"))
            }
    )
    List<Role> selectAllRoleAndRoleMenu();
}

3、Controller
访问/role 的时候roleMenus为null,访问roleMenus的时候就有值了。

package com.xj.controller;

import com.xj.entity.Role;
import com.xj.mapper.RoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class RoleController {
    @Autowired
    private RoleMapper roleMapper;

    @GetMapping("/role")
    public List queryRole(){
        List<Role> list = roleMapper.selectList(null);
        return list;
    }

    @GetMapping("/roleMenus")
    public List queryRoleMenus(){
        List<Role> list = roleMapper.selectAllRoleAndRoleMenu();
        return list;
    }
}

mybatisplus的条件查询

在Controller中加入如下代码:

    @GetMapping("/role/find")
    public List queryRoleByRoleName(){
        QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_key","admin");
        List<Role> roles = roleMapper.selectList(queryWrapper);
        return roles;
    }

结果如下:
在这里插入图片描述

mybatisplus的分页

1、分页的配置文件

package com.xj.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 pageSplitInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        interceptor.addInnerInterceptor(paginationInnerInterceptor);
        return interceptor;
    }
}

2、在Controller写如下代码即可实现分页

    @GetMapping("/roleMenu/findByPage")
    public IPage findRoleMenuBySplitPage(){
        //设置起始值和每页条数
        Page<RoleMenu> roleMenuPage = new Page<>(0,5);
        IPage iPage = roleMenuMapper.selectPage(roleMenuPage, null);
        return iPage;
    }

3、具体效果如下:
在这里插入图片描述

Vue

基本用法

基本用法的代码如下:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <!-- 1、导入vue 的脚本文件,也可以下载到本地,导入本地路径文件 -->
    <script src="https://unpkg.com/vue@3"></script>
</head>
<body>
    <!--2、 声明要被vue所控制的DOM区域 -->
    <div id="app">
        {{message}}
    </div>
    <!-- 3、创建vue的实例对象,这里的实例对象是hello -->
    <script>
        const hello = {
            //指定数据源,即mvvm中的Model
            data:function(){
                return{
                    message:'hello vue3 !'
                }
            }
        }
        const app = Vue.createApp(hello)
        app.mount('#app')
    </script>
</body>
</html>

小技巧:在vscode中如果是html文件,写一个英文感叹号即可获取到html的结构代码。

内容渲染指令

要想html的字符串渲染成html效果,可以用v-html指令

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/vue@3"></script>
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p>姓名:{{username}}</p>
        <p>性别:{{gender}}</p>
        <p>{{desc}}</p>
        <!-- 内容渲染指令 -->
        <p v-html="desc"></p>
    </div>
    <script>
        const person = {
            data:function(){
                return{
                    username:'zhangsan',
                    gender:'男',
                    desc:'<a href="http://www.baidu.com">百度</a>'
                }
            }
        }
        const app = Vue.createApp(person)
        app.mount('#app')
    </script>
</body>
</html>

效果如下:
在这里插入图片描述

属性绑定指令

属性绑定指令方式
:属性名=属性值,属性值从vue声明的变量中获取

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/vue@3"></script>
    <title>Document</title>
</head>
<body>
    <div id="app">
        <a :href="link">百度</a>
        <input type="text" :placeholder="inputValue"></input>
        <img :src="imgSrc" :style="{width:w}">
    </div>
    <script>
        const test03 = {
            data:function(){
                return{
                    link:'http://www.baidu.com',
                    // 文本框的占位内容
                    inputValue:'请输入内容',
                    // 图片的src地址
                    imgSrc:'https://i.niupic.com/images/2020/07/18/8qaW.jpg',
                    w:'500px'
                }
            }
        }
        const app = Vue.createApp(test03)
        app.mount('#app')
        
    </script>
</body>
</html>

效果如下:
在这里插入图片描述

使用JavaScript表达式

可以使用表达式,但是不可以使用语句,如for循环语句

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/vue@3"></script>
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p>{{number+1}}</p>
        <p>{{ok?"True":"False"}}</p>
        <p>{{message.split("").reverse().join("")}}</p>
        <p :id="'list'+id">xxx</p>
        <p>{{user.name}}</p>
    </div>
    <script>
        const test04 = {
            data:function(){
                return{
                    number:9,
                    ok:false,
                    message:"abc",
                    id:3,
                    user:{
                        name:"zhangsan"
                    }
                }
            }
        }
        const app = Vue.createApp(test04)
        app.mount('#app')
    </script>
</body>
</html>

结果如下:
在这里插入图片描述

事件绑定指令

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/vue@3"></script>
    <title>Document</title>
</head>
<body>
    <div id="app">
        <h3>count 的值为:{{count}}</h3>
        <!-- click事件绑定addCount方法,click有两种写法,如下 -->
        <button v-on:click="addCount">+1</button>
        <button @click="addCount">+1</button>
        <!-- 另一种方式实现+1,click点击事件绑定表达式方式 -->
        <button @click = "count +=1">+1</button>
    </div>
    <script>
        const test05 = {
            data:function(){
                return{
                    count:0
                }
            },
            // method 要写在定义的test05对象里面
            methods:{
                addCount(){
                    // 这里的this代表的就是 test05对象
                    this.count += 1
                }
            }
        }
        const app = Vue.createApp(test05)
        app.mount('#app')
    </script>
</body>
</html>

效果如下:
在这里插入图片描述

条件渲染指令

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/vue@3"></script>
    <title>Document</title>
</head>
<body>
    <div id="app">
        <button @click="flag=!flag">点击对flag取反</button>
        <!-- 为false的时候,标签不会被创建 -->
        <p v-if="flag">v-if控制,显示</p>
        <!-- 为false的时候,标签会创建,只是不显示,如果是频繁的切换显示效果,建议用v-show -->
        <p v-show="flag">v-show控制,显示</p>
    </div>
    <script>
        const test06 = {
            data:function(){
                return{
                    flag:false
                }
            }
        }
        const app = Vue.createApp(test06)
        app.mount('#app')
    </script>
</body>
</html>

效果如下:
在这里插入图片描述

v-if 和v-else-if指令

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/vue@3"></script>
    <title>Document</title>
</head>
<body>
    <div id="app">
        <p v-if="num>0.5">随机数大于0.5</p>
        <p v-else>随机数小于或等于0.5</p>
        <hr/>
        <p v-if="type==='a'">优秀</p>
        <p v-else-if="type==='b'">良好</p>
        <p v-else-if="type==='c'">一般</p>
        <p v-else="type==='d'">一般</p>
    </div>
    <script>
        const test07 = {
            data:function(){
                return{
                    num:Math.random(),
                    type:"b"
                }
            }
        }
        const app = Vue.createApp(test07)
        app.mount('#app')
    </script>
</body>
</html>

效果如下:
在这里插入图片描述

列表渲染指令

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/vue@3"></script>
    <title>Document</title>
</head>
<body>
    <div id="app">
       <li v-for="(user,i) in userList">
        索引是:{{i}},
        姓名是:{{user.name}}
       </li>
    </div>
    <script>
        const test08 = {
            data:function(){
                return{
                    userList:[
                        {id:1,name:"aa"},
                        {id:2,name:"bb"},
                        {id:3,name:"cc"},
                    ]
                }
            }
        }
        const app = Vue.createApp(test08)
        app.mount('#app')
    </script>
</body>
</html>

效果如下:
在这里插入图片描述

v-for

v-for中的key

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://unpkg.com/vue@3"></script>
    <title>Document</title>
</head>
<body>
    <div id="app">
        <div>
            <!-- v-model可以实现双向绑定,页面可以拿到定义的变量值,变量也可以拿到页面的值,:value只可以读取到变量值 -->
            <input type="text" v-model="name">
            <button @click="addNewUser">添加</button>
        </div>
        <!-- 用户列表展示区域 -->
        <ul>
            <!-- for循环如果不加索引,系统有的时候会报错,如果不报错,也会出现系统沿用默认的索引,这样我们在选中bb后,再添加一个dd,则选中内容会变成aa -->
            <!-- 如果用默认的index,也会出现这种情况,因为这样添加后,默认的index指向的数据会发生变化 -->
            <!-- 最为正确的是应该添加userlist中的id -->
            <li v-for="(user,index) in userList" :key="user.id">
                <input type="checkbox"/>
                姓名:{{user.name}}
            </li>
        </ul>
     </div>
     <script>
         const test08 = {
             data:function(){
                 return{
                     userList:[
                         {id:1,name:"aa"},
                         {id:2,name:"bb"},
                         {id:3,name:"cc"},
                     ],
                     //输入的用户名,这里如果不设置为空的话,点击添加,输入框会保留之前添加的
                     name:'',
                     // 下一个可用的id值
                     nextid:4
                 }
             },
             methods:{
                //添加按钮调用方法
                addNewUser(){
                    //在原来列表的上方添加内容
                    this.userList.unshift({id:this.nextid,name:this.name})
                    this.name = ''
                    this.nextid++
                }
             }
         }
         const app = Vue.createApp(test08)
         app.mount('#app')
     </script>
</body>
</html>

效果如下:
在这里插入图片描述

npm(node package manager)

node.js的包管理工具,依赖node.js,用来管理前端框架,可以像maven那样下载所需要的依赖包。

vue cli 创建vue项目

是vue官方提供的构建工具,通常成为脚手架,用于快速搭建一个带有热重载(在代码修改后不必刷新页面即可呈现修改后的效果)
1、安装vue cli
在你要创建项目的代码目录运行如下命令:

npm install -g @vue/cli

在这里插入图片描述
2、创建vue项目,输入命令

vue create 项目名称

在这里插入图片描述
3、选择配置
一开始初学,建议选择最后一个选项
在这里插入图片描述
回车后进入下图,去掉这个选项如下图所示(空格可以控制取消和选中):
在这里插入图片描述
4、这里选择vue3
在这里插入图片描述
5、选择配置信息记录在哪个文件下,这里一般是选择package.json,类似maven中的pom.xml
在这里插入图片描述
6、是否保存为模版,这里不保存,输入N即可
在这里插入图片描述
7、开始下载相关依赖,出现如下所示,则表示项目创建完成(下载的时候话的时间有点长,应该不用下载那么多依赖)
在这里插入图片描述
8、运行项目
npm run serve
在这里插入图片描述
9、访问上面的地址,成功访问则表示项目创建是OK的
在这里插入图片描述

项目代码

main.js

// 通过vue,导入vue中的createApp方法
import { createApp } from 'vue'
// 导入App.vue文件
import App from './App.vue'

// 把App内容,注入到app标签中
createApp(App).mount('#app')

vue中组件开发

1、新建文件test.vue,组件代码

// 组件内容
<template>
    <h1> 组件测试</h1>
</template>
// 组件动作
<script>
</script>
// 组件样式
<style>
</style>

2、App.vue中使用:

// 这个文件其实也是一个组件,最终是渲染到了index.html中,因为main.js引用了这个文件内容,最后注入到了app这个id中。
<template>
  <img alt="Vue logo" src="./assets/logo.png">
  <!-- 使用组件 -->
  <test></test>
  <HelloWorld msg="Welcome to Your Vue.js App"/>
</template>

<script>
import HelloWorld from './components/HelloWorld.vue';
// 引入test组件
import test from './components/test.vue'

export default {
  name: 'App',
  components: {
    HelloWorld,
    test
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

第三方组件element-ui

创建一个vue项目,基于vue 2.x

一个简单的电影组件:

Movie.vue

<template>
    <div>
        <h1>{{title}}</h1>
        <span>{{rating}}</span>
        <button @click="funSc">点击收藏</button>
    </div>
</template>
<script>
export default {
    name:"Movie",
    props:["title","rating"],
    data:function(){
        return{

        }
    },
    methods:{
        funSc(){
            alert("收藏成功")
        }
    }
}
</script>

App.vue 组件中使用 Movie.vue 组件
App.vue

<template>
  <div id="app">
    <Movie v-for="movie in movies" :key="movie.id" :title="movie.title" :rating="movie.rating"></Movie>
  </div>
</template>

<script>
import Movie from './components/Movie.vue'

export default {
  name: 'App',
  data:function(){
    return{
      movies:[
        {id:1,title:"叶问1",rating:9.4},
        {id:2,title:"叶问2",rating:8.5},
        {id:3,title:"叶问3",rating:8.9}
      ]
    }
  },
  components: {
    Movie
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

element-ui介绍

Element是国内饿了么公司提供的一套开源前端框架
文档地址:https://element.eleme.cn/#/zh-CN/

element-ui安装

安装命令:npm i element-ui -S
在这里插入图片描述
这里提示下载了9个包,这9个包放到了node_modules中,并且在package.json中会有记录
在这里插入图片描述

我们在把项目传递给其他人的时候,不需要把node_modules中的包也发过去,只需要把package.json文件发过去就可以了,然后在项目的控制台运行 npm install 命令即可把package.json中的依赖下载会node_modules目录中。所以如果你在网上下载下来的项目运行不了(npm run serve),你应该先去检查一下有没有node_modules目录,如果没有则运行一下npm install,然后再去运行项目。

element-ui引入

在 main.js 中写入以下内容:

import Vue from 'vue'
//引入 ElementUI
import ElementUI from 'element-ui';
//引入 ElementUI 的css供ElementUI中的组件使用
import 'element-ui/lib/theme-chalk/index.css';

import App from './App.vue'

//全局注册
Vue.use(ElementUI);

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

element-ui使用,以table表格为例

参考官方文档table组件
添加组件HelloElTable,代码如下,

<template>
  <el-table
    :data="tableData"
    style="width: 100%"
    :row-class-name="tableRowClassName"
  >
    <el-table-column prop="date" label="日期" width="180"> </el-table-column>
    <el-table-column prop="name" label="姓名" width="180"> </el-table-column>
    <el-table-column prop="address" label="地址"> </el-table-column>
  </el-table>
</template>

<style>
  .el-table .warning-row {
    background: oldlace;
  }

  .el-table .success-row {
    background: #f0f9eb;
  }
</style>

<script>
export default {
  methods: {
    tableRowClassName({ row, rowIndex }) {
      if (rowIndex === 1) {
        return "warning-row";
      } else if (rowIndex === 3) {
        return "success-row";
      }
      return "";
    },
  },
  data() {
    return {
      tableData: [
        {
          date: "2016-05-02",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1518 弄",
        },
        {
          date: "2016-05-04",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1518 弄",
        },
        {
          date: "2016-05-01",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1518 弄",
        },
        {
          date: "2016-05-03",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1518 弄",
        },
      ],
    };
  },
};
</script>

在App.vue中引入
如下:
在这里插入图片描述
代码如下:

<template>
  <div id="app">
    <Movie v-for="movie in movies" :key="movie.id" :title="movie.title" :rating="movie.rating"></Movie>
    <HelloElTable></HelloElTable>
  </div>
</template>

<script>
import Movie from './components/Movie.vue'
import HelloElTable from './components/HelloElTable.vue'

export default {
  name: 'App',
  data:function(){
    return{
      movies:[
        {id:1,title:"叶问1",rating:9.4},
        {id:2,title:"叶问2",rating:8.5},
        {id:3,title:"叶问3",rating:8.9}
      ]
    }
  },
  components: {
    Movie,
    HelloElTable
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

index引入App.vue中的id
在这里插入图片描述
代码如下:

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

启动vue项目,index.html显示如下:
在这里插入图片描述

第三方图标库

由于element ui提供的字体图标较少,一般会采用其他图标库,如著名的font awesome
文档地址:https://fontawesome.dashgame.com/
安装
控制台输入命令:npm install font-awesome
使用
在man.js中引入 import ‘font-awesome/css/font-awesome.min.css’
具体代码如下:

import Vue from 'vue'
//引入 ElementUI
import ElementUI from 'element-ui';
//引入 ElementUI 的css供ElementUI中的组件使用
import 'element-ui/lib/theme-chalk/index.css';
//引入图标库
import 'font-awesome/css/font-awesome.min.css'
import App from './App.vue'

//全局注册
Vue.use(ElementUI);

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

HelloElTable.vue 组件中使用如下:

<template>
<!-- template中只能放一个标签,所以可以在最外层套一个div标签,这样就可以并列放其他标签,如图标标签i -->
  <div>
    <el-table
      :data="tableData"
      style="width: 100%"
      :row-class-name="tableRowClassName"
    >
      <el-table-column prop="date" label="日期" width="180"> </el-table-column>
      <el-table-column prop="name" label="姓名" width="180"> </el-table-column>
      <el-table-column prop="address" label="地址"> </el-table-column>
    </el-table>
    <i class="fa fa-camera-retro fa-lg">fa-lg</i> 
    <i class="fa fa-automobile fa-lg">fa-lg</i>
  </div>
</template>

<style>
.el-table .warning-row {
  background: oldlace;
}

.el-table .success-row {
  background: #f0f9eb;
}
</style>

<script>
export default {
  methods: {
    tableRowClassName({ row, rowIndex }) {
      if (rowIndex === 1) {
        return "warning-row";
      } else if (rowIndex === 3) {
        return "success-row";
      }
      return "";
    },
  },
  data() {
    return {
      tableData: [
        {
          date: "2016-05-02",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1518 弄",
        },
        {
          date: "2016-05-04",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1518 弄",
        },
        {
          date: "2016-05-01",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1518 弄",
        },
        {
          date: "2016-05-03",
          name: "王小虎",
          address: "上海市普陀区金沙江路 1518 弄",
        },
      ],
    };
  },
};
</script>

效果如下:
在这里插入图片描述
如果要使用其他的字体图标,直接在官网中点击图标右侧的赋值图标标签,然后替换图标即可
在这里插入图片描述
除了上面npm install 添加包然后本地引入外还可以用其他方式引入,如:
将以下代码粘贴到网页HTML代码的 部分

<link href="//netdna.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">

Axios网络请求

Axios介绍

用于前端页面访问后端服务器

Axios安装

npm install axios
官网地址:https://www.axios-http.cn/

Axios引入

//引入Axios
import axios from 'axios';

Axios使用

vue生命周期

Movie.vue

<template>
    <div>
        <h1>{{title}}</h1>
        <span>{{rating}}</span>
        <button @click="funSc">点击收藏</button>
    </div>
</template>
<script>
export default {
    name:"Movie",
    props:["title","rating"],
    data:function(){
        return{

        }
    },
    //vue的生命周期函数,如created,mounted
    created:function(){
        console.log("Movie 组件被创建")
    },
    //method中写的是自定义方法
    methods:{
        funSc(){
            alert("收藏成功")
        }
    }
}
</script>

App.vue

<template>
  <div id="app">
    <Movie v-for="movie in movies" :key="movie.id" :title="movie.title" :rating="movie.rating"></Movie>
    <HelloElTable></HelloElTable>
  </div>
</template>

<script>
import Movie from './components/Movie.vue'
import HelloElTable from './components/HelloElTable.vue'

export default {
  name: 'App',
  data:function(){
    return{
      movies:[
        {id:1,title:"叶问1",rating:9.4},
        {id:2,title:"叶问2",rating:8.5},
        {id:3,title:"叶问3",rating:8.9}
      ]
    }
  },
  created:function(){
    console.log("App 组件被创建")
  },
  mounted:function(){
    console.log("App 组件挂载完毕")
  },
  components: {
    Movie,
    HelloElTable
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

显示结果如下:
在这里插入图片描述
这里movie组件被创建多次,是因为在for循环中使用了这个组件:
在这里插入图片描述

跨域问题

为什么会出现跨域问题:
为了保证浏览器的安全,不同源的客户端脚本在没有明确授权的情况下,不能读写对方的资源,称为同源策略,同源策略是浏览器安全的基石。
所谓同源(即指在同一个域)就是两个页面具有相同的协议(protocol),主机(host)和端口号(port)
当一个请求url的协议、域名、端口三者之间任意一个与当前页面所在的url不同,即视为跨域
App.vue

<template>
  <div id="app">
    <Movie v-for="movie in movies" :key="movie.id" :title="movie.title" :rating="movie.rating"></Movie>
    <HelloElTable></HelloElTable>
  </div>
</template>

<script>
import Movie from './components/Movie.vue'
import HelloElTable from './components/HelloElTable.vue'
import axios from 'axios'

export default {
  name: 'App',
  data:function(){
    return{
      movies:[
        {id:1,title:"叶问1",rating:9.4},
        {id:2,title:"叶问2",rating:8.5},
        {id:3,title:"叶问3",rating:8.9}
      ]
    }
  },
  created:function(){
    console.log("App 组件被创建")
    //一般组件请求都是写在这里的
    //这里如果不写协议、域名、端口的话,系统会默认访问当前页面的协议、域名、端口。
    // axios.get("/user/findAll").then(function(res){
    //   console.log(res)
    // })
    axios.get("http://localhost:8023//roleMenus").then(function(res){
      console.log(res)
    })
  },
  mounted:function(){
    console.log("App 组件挂载完毕")
  },
  components: {
    Movie,
    HelloElTable
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

出现如下跨域问题
在这里插入图片描述

跨域问题解决

CORS(Cross-Origin Resource Sharing)是由W3C制定的一种跨域资源共享技术,其目的就是为了解决前端的跨域请求。
CORS可以再不破坏既有规则的情况下,通过后端服务器实现CORS接口,从而实现跨域通信。
Springboot提供了在Controller中添加注解的方式来解决跨域问题:
@CrossOrigin

package com.xj.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.xj.entity.Role;
import com.xj.mapper.RoleMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
@CrossOrigin
public class RoleController {
    @Autowired
    private RoleMapper roleMapper;

    @GetMapping("/role")
    public List queryRole(){
        List<Role> list = roleMapper.selectList(null);
        return list;
    }

    @GetMapping("/role/find")
    public List queryRoleByRoleName(){
        QueryWrapper<Role> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("role_key","admin");
        List<Role> roles = roleMapper.selectList(queryWrapper);
        return roles;
    }

    @GetMapping("/roleMenus")
    public List queryRoleMenus(){
        List<Role> list = roleMapper.selectAllRoleAndRoleMenu();
        return list;
    }

   @GetMapping("/role/findByPage")
    public IPage findRoleByPage(){ 
        //设置起始值和每页数据条数
        Page<Role> page = new Page<>(0,5);
        IPage iPage = roleMapper.selectPage(page,null);
        return iPage;
    }
}

HelloElTable.vue

<template>
  <!-- template中只能放一个标签,所以可以在最外层套一个div标签,这样就可以并列放其他标签,如图标标签i -->
  <div>
    <el-table
      :data="tableData"
      style="width: 100%"
      :row-class-name="tableRowClassName"
    >
      <el-table-column prop="roleName" label="角色名称" width="180"> </el-table-column>
      <el-table-column prop="roleKey" label="角色key" width="180"> </el-table-column>
    </el-table>
    <i class="fa fa-camera-retro fa-lg">fa-lg</i>
    <i class="fa fa-automobile fa-lg">fa-lg</i>
  </div>
</template>

<style>
.el-table .warning-row {
  background: oldlace;
}

.el-table .success-row {
  background: #f0f9eb;
}
</style>

<script>
import axios from 'axios'
export default {
  methods: {
    tableRowClassName({ row, rowIndex }) {
      if (rowIndex === 1) {
        return "warning-row";
      } else if (rowIndex === 3) {
        return "success-row";
      }
      return "";
    },
  },
  created: function () {
    //function(res){} 无法继承方法外的this
    //(res)=>{}这种方式定义函数,函数内可以继承外面的this作用域
    axios.get("http://localhost:8023//roleMenus").then((res)=>{
      this.tableData = res.data
    })
  },
  data() {
    return {
      tableData: [],
    };
  },
};
</script>

最终成功访问,展示效果如下:
在这里插入图片描述
这里注意一下:在实际开发中,我们axios的引入在main.js中就可以了
代码如下:
main.js

import Vue from 'vue'
import App from './App.vue'
//引入 ElementUI
import ElementUI from 'element-ui';
//引入 ElementUI 的css供ElementUI中的组件使用
import 'element-ui/lib/theme-chalk/index.css';
//引入图标库
import 'font-awesome/css/font-awesome.min.css'
//引入Axios
import axios from 'axios';

//这里注册baseUrl,这样的话修改了域名和端口号就不用每个地址都去修改了
axios.defaults.baseURL = "http://localhost:8023"
Vue.prototype.$http = axios

//全局注册
Vue.use(ElementUI);

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

HelloElTable.vue

<template>
  <!-- template中只能放一个标签,所以可以在最外层套一个div标签,这样就可以并列放其他标签,如图标标签i -->
  <div>
    <el-table
      :data="tableData"
      style="width: 100%"
      :row-class-name="tableRowClassName"
    >
      <el-table-column prop="roleName" label="角色名称" width="180"> </el-table-column>
      <el-table-column prop="roleKey" label="角色key" width="180"> </el-table-column>
    </el-table>
    <i class="fa fa-camera-retro fa-lg">fa-lg</i>
    <i class="fa fa-automobile fa-lg">fa-lg</i>
  </div>
</template>

<style>
.el-table .warning-row {
  background: oldlace;
}

.el-table .success-row {
  background: #f0f9eb;
}
</style>

<script>
import axios from 'axios'
export default {
  methods: {
    tableRowClassName({ row, rowIndex }) {
      if (rowIndex === 1) {
        return "warning-row";
      } else if (rowIndex === 3) {
        return "success-row";
      }
      return "";
    },
  },
  created: function () {
    //function(res){} 无法继承方法外的this
    //(res)=>{}这种方式定义函数,函数内可以继承外面的this作用域
    this.$http.get("/roleMenus").then((res)=>{
      this.tableData = res.data
    })
  },
  data() {
    return {
      tableData: [],
    };
  },
};
</script>

前端路由VueRouter

介绍

适用于单页面

安装

命令:npm install vue-router 默认安装最新的版本
安装3版本对应vue2,命令:npm install vue-router@3
安装4版本对应vue3,命令:npm install vue-router@4

使用

1、组件目录(components)新建如下组件
Discover.vue

<template>
    <div>
        <h1>发现音乐</h1>
    </div>
</template>

Friends.vue

<template>
    <div>
        <h1>关注</h1>
    </div>
</template>

My.vue

<template>
    <div>
        <h1>我的音乐</h1>
    </div>
</template>

2、src目录下新建router目录,目录下新建index.js
代码如下:

import Vue from 'vue';
import VueRouter from 'vue-router';
import Discover from '../components/Discover.vue'
import Friends from '../components/Friends.vue'
import My from '../components/My.vue'

//全局注册
Vue.use(VueRouter);

const router = new VueRouter({
    //制定属性和组件的对应关系
    routes:[
        {path:'/discover',component:Discover},
        {path:'/friends',component:Friends},
        {path:'/my',component:My},
    ]
})

export default router

3、main.js中导入router
如图
在这里插入图片描述
具体代码如下:

import Vue from 'vue'
import App from './App.vue'
//引入 ElementUI
import ElementUI from 'element-ui';
//引入 ElementUI 的css供ElementUI中的组件使用
import 'element-ui/lib/theme-chalk/index.css';
//引入图标库
import 'font-awesome/css/font-awesome.min.css'
//引入Axios
import axios from 'axios';

//js的名字为index,可以不用再往下写名字了
// import router from './router/index'
import router from './router'

//这里注册baseUrl,这样的话修改了域名和端口号就不用每个地址都去修改了
axios.defaults.baseURL = "http://localhost:8023"
Vue.prototype.$http = axios

//全局注册
Vue.use(ElementUI);

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

3、在App.vue中使用
在这里插入图片描述
代码如下:

<template>
  <div id="app">
    <!-- <Movie v-for="movie in movies" :key="movie.id" :title="movie.title" :rating="movie.rating"></Movie>
    <HelloElTable></HelloElTable> -->
    <!-- 声明路由链接 -->
    <router-link to="/discover">发现音乐  </router-link>
    <router-link to="/my">我的音乐  </router-link>
    <router-link to="/friends">关注</router-link>
    <!-- 声明路由占位标签,选择上面中的一个就会填充到下面的占位标签 -->
    <router-view></router-view>
  </div>
</template>

<script>
import Movie from './components/Movie.vue'
import HelloElTable from './components/HelloElTable.vue'
import axios from 'axios'

export default {
  name: 'App',
  data:function(){
    return{
      movies:[
        {id:1,title:"叶问1",rating:9.4},
        {id:2,title:"叶问2",rating:8.5},
        {id:3,title:"叶问3",rating:8.9}
      ]
    }
  },
  created:function(){
    console.log("App 组件被创建")
    //一般组件请求都是写在这里的
    //这里如果不写协议、域名、端口的话,系统会默认访问当前页面的协议、域名、端口。
    // axios.get("/user/findAll").then(function(res){
    //   console.log(res)
    // })
  },
  mounted:function(){
    console.log("App 组件挂载完毕")
  },
  components: {
    Movie,
    HelloElTable
  }
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

4、如果要首页定为其中一个组件,可以在index.js中加如下代码:
在这里插入图片描述

Vuex

介绍

Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化

安装

npm install vuex@3

使用

基本的使用

src下创建store目录,store目录下新建index.js,代码如下:

import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
    state: {
        count: 0
    },
    mutations: {
        increment(state) {
            state.count++
        }
    }
})

// 导出
export default store

在mian.js中引入,这样在所有的组件中就可以使用了,代码如下:

// 引入vuex
import store from './store';

new Vue({
  render: h => h(App),
  store
}).$mount('#app')

在组件中使用,如下即可拿到count的取值。

<template>
    <div>
        {{this.$store.state.count}}
    </div>
</template>

如果你要给count做+1,可以调用 mutations 中的方法
具体代码如下:

<template>
    <div>
        {{this.$store.state.count}}
        <button @click="add">+1</button>
    </div>
</template>

<script>
export default {
    methods:{
        add(){
            this.$store.commit("increment")
        }
    }
}
</script>

{{this.$store.state.count}} 这种在标签的写法可以简写成
在这里插入图片描述

<template>
 <div>
   {{count}}
   <!-- {{ this.$store.state.count }} -->
   <button @click="add">+1</button>
 </div>
</template>

<script>
import { mapState } from 'vuex'
export default {
 computed: mapState([
   // 映射 this.count 为 store.state.count
   "count",
 ]),
 methods: {
   add() {
     this.$store.commit("increment");
   },
 },
};
</script>

Vue-Element-admin

vue-element-admin是一个后台前端解决方案,它基于vue和element-ui实现

下载项目

官网:https://panjiachen.gitee.io/vue-element-admin-site/zh/guide/#%E5%8A%9F%E8%83%BD
在这里插入图片描述

这里以基础版下载为例
git 下载:git clone https://github.com/PanJiaChen/vue-admin-template.git
也可直接到代码库下载zip包,地址:https://github.com/PanJiaChen/vue-admin-template

安装和使用

进入项目目录
cd vue-admin-template
安装依赖
npm install
启动服务
npm run dev
具体逻辑看代码

云服务器的使用

云服务器概念

云服务器(Elastic Computer Service,ECS ),Elastic 弹性的,是一种简单高效、安全可靠、处理能力可弹性伸缩的计算服务。其管理方式比物理服务器更加简单高效。

阿里云ECS的使用

地址:https://www.aliyun.com/
1、我们一般选择云服务器(ECS)
在这里插入图片描述
2、购买云服务器地域选择,用户集中在哪就应该选择离用户近的
在这里插入图片描述
3、选择规格,如下选择的是:CPU两核内存2g
在这里插入图片描述

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
回答: Spring Boot 是一个用于快速构建基于Spring框架的应用程序的开发框架。它提供了很多的Starter,用于自动配置第三方库。在Spring Boot中,我们可以使用@SpringBootApplication注解来代替@EnableAutoConfiguration和@ComponentScan注解。\[1\]如果你想在Spring Boot中使用Vue进行全栈开发,你可以按照以下步骤进行操作: 1. 创建一个Spring Boot项目,并在启动类中添加@SpringBootApplication注解。\[1\] 2. 导入Vue的相关依赖,可以使用Maven或者其他构建工具进行管理。\[2\] 3. 创建一个RestController类,使用@RestController注解来标识该类为一个控制器。在该类中,可以定义各种接口来处理前端的请求。\[3\] 4. 在接口方法中编写相应的业务逻辑,返回前端需要的数据。 5. 在前端部分,你可以使用Vue来构建用户界面,并通过发送HTTP请求来与后端进行通信。 通过以上步骤,你可以实现Spring BootVue全栈开发。你可以根据具体的需求和业务逻辑来扩展和定制你的应用程序。 #### 引用[.reference_title] - *1* *3* [学习SpringBoot+Vue全栈开发实战](https://blog.csdn.net/qq_35849321/article/details/106079398)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] - *2* [springboot+vue全栈开发](https://blog.csdn.net/qq_45811584/article/details/128666030)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值