从零开始写一个 SpringBoot + Vue 小项目

引言

在当今的互联网时代,前后端分离技术已经越来越受到重视。作为一个轻量级的 Java Web 开发框架,Spring Boot 可以帮助我们快速构建一个独立运行、产品级别的服务。而 Vue.js 作为一个简单而高效的前端框架,也是前后端分离开发中不可忽视的一环。

那么,如果你想学习前后端分离技术,学习 Spring Boot 和 Vue 是一个不错的选择。为了方便理解这些技术,我构想了一个叫做"梦想屋"的小项目,用来练习学习 Spring Boot 和 Vue。在本文中,我将分享这个小项目的设计和实现过程,希望能帮助到正在学习前后端分离的初学者。

当然作者本人也是一位初学者,从开始学习 Spring Boot 和 Vue 到写完这篇文章的时间不足十天,所以在文章中如果有什么技术层面的错误或不妥的地方,希望各位大佬能帮助指正一下。

温馨提示:

  • 建议有些许 Java、JavaWeb、MySQL、HTML、CSS、JavaScript 基础的学习效果更佳
  • 本文较长,建议配合目录阅读

正文部分

本次项目中用到的软件和工具

  • 后端开发软件:IntelliJ IDEA
  • 前端开发软件:Visual Studio Code
  • 服务器连接工具:Termius
  • 数据库:MySQL
  • 数据库管理工具:Navicat Premium
  • 数据库连接工具:MyBatis
  • API文档生成工具:Swagger
  • API文档美化工具:Knife4j
  • UI组件库:Element
  • 网络请求库:Axios
  • 字体处理库:Sfntly
  • JSON处理工具:Fastjson
  • Java工具库:Lombok

可以不必全部用这些来做,有很多类似的产品可以替代

Spring Boot 后端

创建 Spring Boot 项目

首先我们打开 IDEA,点击新建项目,选择 Spring Initializr,然后在右侧填写项目名称,类型选择 Maven,JDK 版本选择1.8,如下图所示,然后点击下一步。

图示 1

在新的页面中选择 Spring Boot 版本 2.7.9(SNAPSHOT),其他的保持默认,点击创建。

图示 2

项目目录结构

创建完项目之后展开左侧项目目录。

图示 3

可以看到 Spring Boot 的基础结构有三个文件:

  • src/main/java 下的程序入口:DreamSpringApplication
  • src/main/resources 下的配置文件:application.properties
  • src/test 下的测试入口:DreamSpringApplicationTests

四层架构

在运行类 DreamSpringApplication 同级目录下创建 controller、service、mapper、model 四个目录

图示 4

  • controller 层

    • 控制层
    • 作用是请求和响应控制
    • 负责前后端交互,接受前端请求,调用 service 层,接收 service 层返回的数据,最后返回具体的页面和数据到客户端
  • service 层

    • 业务逻辑层
    • 作用是完成功能设计
    • 调用 mapper 层接口,接收 mapper 层返回的数据,完成项目的基本功能设计
  • mapper 层

    • 数据持久层,也被称为 dao 层
    • 作用是访问数据库,向数据库发送 sql 语句,完成数据的增删改查任务
  • model 层

    • 数据库实体层,也被称为 entity 层、pojo 层
    • 用于存储数据库中的数据,类属性与数据库表字段对应
    • 通常情况下,Model 层使用 ORM(对象关系映射)技术,如 JPA,MyBatis等与数据库进行交互

项目依赖

在项目目录中的 pom.xml 配置文件中引入本次项目中需要用到的相关依赖

<!--Lombok-->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

<!--Swagger3-->
<dependency>
    <groupId>io.springfox</groupId>
    <artifactId>springfox-boot-starter</artifactId>
    <version>3.0.0</version>
</dependency>

<!--knife4j-->
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-ui</artifactId>
    <version>3.0.3</version>
</dependency>

<!--MyBatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>

<!--MySQL-->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.0.31</version>
    <scope>runtime</scope>
</dependency>

<!--JSON-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.15</version>
</dependency>

注:
可能有些新手小白没用过 Maven 的,不知道该把依赖引入到哪个位置,我第一次用 Maven 的时候也不知道。
所以也在这里说一下,将这些依赖放到<dependencies></dependencies>标签内,就接着 Spring Boot 自带的一些依赖下面就行了。

稍微解释一下这些依赖

  • Lombok:可以通过注解的方式为 Java 类生成许多常用的方法,例如 getter、setter、toString、equals 等,省去了手写这些方法的代码。

  • Swagger:开源的 API 文档生成工具,可以根据代码中的注释自动生成 API 文档。

  • Knife4j:用来美化 Swagger 生成的 API 文档。

  • MyBatis:持久层框架,主要用于简化数据库操作,提高代码可读性,降低数据访问代码的维护难度,提高效率。

  • MySQL:MySQL 官方提供的 Java 用的 JDBC 驱动,它允许 Java 程序通过 JDBC API 连接到 MySQL 数据库。

  • JSON:用来将 json 数据序列化和反序列化的,主要是觉得 SpringBoot 自带的 json 工具不好用。

创建 MySQL 数据库

安装 MySQL 的过程我这里就不讲了,这个可以找对应操作系统的相关教程对着安装就行了,安装在本地也是可以的。

由于我这边项目最终是要部署在服务器上运行的,所以我就直接将数据库创建在服务器上了。

安装完 MySQL 后,用 Navicat Premium 连接数据库(其他数据库管理工具也可以),填写连接名、主机IP、端口、用户名和密码等,然后点击连接。

图示 5

连接上数据库之后,新建数据库–>新建表–>添加 id、ip、province、time、str、likes 六个字段。

图示 6

配置文件

打开刚刚介绍到的配置文件 application.properties 进行项目配置


server.port = 8087

spring.mvc.pathmatch.matching-strategy = ant_path_matcher

# Swagger
swagger.enabled = true

# MySQL
spring.datasource.url = jdbc:mysql:(服务器IP地址):3306/(数据库名)
spring.datasource.username = (用户名)
spring.datasource.password = (密码)
spring.datasource.driver-class-name = com.mysql.cj.jdbc.Driver
  • server.port:指定项目使用 8087 端口,默认 8080 端口

  • spring.mvc.pathmatch.matching-strategy:指定Spring MVC框架中URL路径匹配策略的实现类的类名,ant_path_matcher 是一种路径匹配策略,使用Ant风格的通配符来匹配URL路径

  • swagger.enabled:启用Swagger工具

  • spring.datasource.url:指定MySQL数据库的URL

  • spring.datasource.username:指定连接MySQL数据库使用的用户名

  • spring.datasource.password:指定连接MySQL数据库使用的密码

  • spring.datasource.driver-class-name:指定连接MySQL数据库使用的JDBC驱动程序的类名

实现增删改查接口

model 层

在 model 目录下新建 Data 类

图示 7

在上面已经讲解过 model 层的作用了,所以就直接上代码,代码中的参数与数据库表中的字段对应。

@lombok.Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("数据库字段")
public class Data {
    
    

    @ApiModelProperty(value = "信息所属ID", required = true, example = "1")
    private int id;

    @ApiModelProperty(value = "信息来源IP地址", required = true, example = "127.0.0.1")
    private String ip;

    @ApiModelProperty(value = "信息来源所属省份", required = true, example = "湖北")
    private String province;

    @ApiModelProperty(value = "内容发布时间", required = true, example = "2023-2-6 16:58:00")
    private String time;

    @ApiModelProperty(value = "梦想内容", required = true, example = "环游世界!")
    private String str;

    @ApiModelProperty(value = "点赞数", required = true, example = "52")
    private int likes;

}

能学到SpringBoot框架这里,说明大家应该都是有Java基础的,所以接下来说一下这段代码中的各个注解的作用

  • @lombok.Data:这是 Lombok 框架提供的注解,它会自动生成 getter、setter、toString、equals、hashCode 等方法。使用该注解可以简化代码,并提高开发效率。

  • @NoArgsConstructor:这也是 Lombok 提供的注解,它会生成一个无参构造器,可以避免手动编写无参构造器。这个注解常用于一些框架或工具的实例化。

  • @AllArgsConstructor:同样是 Lombok 提供的注解,它会生成一个全参构造器,可以避免手动编写全参构造器。这个注解也常用于一些框架或工具的实例化。

  • @ApiModel:这是 Swagger 框架提供的注解,用于描述一个模型类。这个注解的作用是将模型类描述为一个 API 文档的模型,可以通过该注解指定模型类的名称和描述信息。

  • @ApiModelProperty:也是 Swagger 框架提供的注解,用于描述模型类中的属性信息。该注解可以设置属性的名称、描述、是否必需等信息,以便在 Swagger 生成的 API 文档中显示。

    • value:属性的描述信息,用于在 API 文档中显示该属性的作用。

    • required:属性是否必需。当该值为 true 时,表示该属性必须包含在请求中;当该值为 false 时,表示该属性可以为空或者不包含在请求中。

    • example:属性的示例值。用于在 API 文档中显示该属性的样例值,方便开发者理解该属性的类型和取值范围。

mapper 层

在 mapper 目录下新建 DataMapper 接口

图示 8

mapper 层的作用在上面也讲过了,主要是用于数据库操作的,直接上代码

@Mapper
public interface DataMapper {
    
    

    /**
     * 信息来源IP地址
     * @param ip
     * 信息来源省份
     * @param province
     * 信息发出时间
     * @param time
     * 信息内容
     * @param str
     * 点赞数
     * @param likes
     *
     * @return
     */
    @Insert("insert into dream (ip, province, time, str, likes) values(#{ip}, #{province}, #{time}, #{str}, #{likes})")
    int insert(@Param("ip") String ip,
               @Param("province") String province,
               @Param("time") String time,
               @Param("str") String str,
               @Param("likes") int likes);

    /**
     * 信息id
     * @param id
     *
     * @return
     *
     * property属性对应Data对象中的成员名,column对应select出的字段名。
     */
    @Results({
    
    
            @Result(property = "id", column = "id"),
            @Result(property = "ip", column = "ip"),
            @Result(property = "province", column = "province"),
            @Result(property = "time", column = "time"),
            @Result(property = "str", column = "str"),
            @Result(property = "likes", column = "likes")
    })
    @Select("select * from dream where id = #{id}")
    Data findById(@Param("id") int id);

    /**
     * 用Data对象来作为传参,这样语句中的#{id}、#{ip}等数据就分别对应Data对象中的id和ip等属性。
     *
     * @param data
     */
    @Update("update dream set ip=#{ip}, province=#{province}, time=#{time}, str=#{str}, likes=#{likes} where id=#{id}")
    void update(Data data);

    /**
     * 删除该id对应的信息
     *
     * @param id
     */
    @Delete("delete from dream where id =#{id}")
    void delete(int id);

}

相关注解的作用

  • @Mapper:是 MyBatis 框架提供的注解,用于标记一个 Java 接口,该接口用于定义数据访问方法。在使用 @Mapper 注解后,MyBatis 会自动扫描该接口,为其创建一个代理对象。该代理对象可以将接口方法与 MyBatis 的 SQL 映射文件中的 SQL 语句进行绑定,并完成数据访问的操作。

  • @Insert:也是 MyBatis 框架提供的注解,该注解的值为 SQL 语句,用于指定插入操作的具体逻辑。该 SQL 语句使用了预处理语句,从而避免了 SQL 注入的问题。

  • @Param

    • /** */中的内容是 JavaDoc(Java文档注释),它用于对方法进行说明、描述和文档化。

    • 在方法中的 @Param 注解用于指定参数的名称,以便在 SQL 语句中使用相应的占位符。

  • @Results:用于定义从查询结果集中将查询结果映射为 Java 对象的过程。

  • @Select:同样是 MyBatis 框架提供的注解,该注解的值为 SQL 语句,用于指定查询操作的具体逻辑。

  • @Update:MyBatis 框架提供的注解,用于指定更新操作的 SQL 语句。

  • @Delete:MyBatis 框架提供的注解,用于指定删除操作的 SQL 语句。

service 层

在 service 目录下新建 DataService 类

图示 9

service 层的作用在前面也讲过了,主要是用于业务逻辑处理,直接上代码

@Service
public class DataService {
    
    
    @Autowired
    private DataMapper dataMapper;

    /**
     * 新增信息
     *
     * @param ip
     * @param province
     * @param time
     * @param str
     * @param likes
     * @return
     */
    public String insert(String ip, String province, String time, String str, int likes) {
    
    
        dataMapper.insert( ip, province, time, str, likes);
        return "succeed";
    }

    /**
     * 查询id对应的信息
     *
     * @param id
     * @return
     */
    public Data findById(int id) {
    
    
        return dataMapper.findById(id);
    }

    /**
     * 更新信息
     *
     * @param data
     */
    public void update(Data data) {
    
    
        dataMapper.update(data);
    }

    /**
     * 删除id对应的信息
     *
     * @param id
     */
    public void delete(int id) {
    
    
        dataMapper.delete(id);
    }

}

相关注解的作用

  • @Service:用于标注一个类为 Spring 框架中的一个服务类,该类中通常包含了业务逻辑的实现。使用该注解可以使 Spring 框架自动扫描并将该类实例化,并将其作为服务类注册到容器中,以供其他组件使用。当我们需要在其他类中使用该服务类时,只需要通过依赖注入的方式获取该类的实例即可。

  • @Autowired:用于实现 Spring 框架中的自动装配功能,将需要使用的 Bean 对象注入到指定的属性中。通过使用该注解,可以避免手动创建 Bean 实例和手动注入对象的麻烦。

controller 层

在 controller 目录下新建 DataController 类

图示 10

本层的作用在上面也讲过了,直接上代码

@Api(tags="API接口")
@RestController
@CrossOrigin(origins = "*",maxAge = 3600)
public class DataController {
    
    

    @Autowired
    private DataService dataService;

    @ApiOperation("添加完整信息")
    @PostMapping("/insert")
    public String insert(@RequestBody Data data) {
    
    
        // @RequestBody注解用来绑定通过http请求中application/json类型上传的数据

        return dataService.insert(data.getIp(), data.getProvince(), data.getTime(), data.getStr(), data.getLikes());
    }

    @ApiOperation("查询id对应的信息")
    @GetMapping("/findById/{id}")
    public Data findById(@PathVariable int id) {
    
    

        return dataService.findById(id);
    }

    @ApiOperation("更新信息")
    @PutMapping("/update")
    public void update(@RequestBody Data data) {
    
    

        dataService.update(data);
    }

    @ApiOperation("删除指定id的信息")
    @DeleteMapping("/delete/{id}")
    public void deleteUser(@PathVariable int id) {
    
    

        dataService.delete(id);
    }
}

相关注解的作用

  • @Api:Swagger 的注解之一,用于对 API 接口进行注释和说明。tags 属性是 Swagger 文档中的一个重要属性,可以用来将 API 接口进行分类,方便管理和查找。

  • @RestController:Spring MVC 中的注解之一,用于标识该类是一个基于 RESTful 风格的 Web 服务类。

  • @CrossOrigin:Spring 中的一个注解,用于支持跨域请求。跨域请求通常指在一个域名下的页面中使用 AJAX 技术向不同的域名或端口号的 Web 服务发送请求。

  • @ApiOperation:Swagger 的注解之一,用于对 API 接口中的具体操作进行注释和说明。

  • @PostMapping:Spring MVC 中的注解之一,表示该方法接收 POST 请求。

  • @RequestBody:Spring MVC 中的注解之一,表示该方法接收的请求参数为请求体中的数据。

  • @GetMapping:Spring MVC 中的注解之一,表示该方法接收 GET 请求。

  • @PathVariable:Spring MVC 中的注解之一,表示该方法接收的请求参数为路径参数。

  • @PutMapping:Spring MVC 中的注解之一,表示该方法接收 PUT 请求。

  • @DeleteMapping:Spring MVC 中的注解之一,表示该方法接收 DELETE 请求。

注:
可能有些人在使用这些注解的时候报错无法解析,大概率是因为SpringBoot项目中没有自带相关的依赖,如果出现这样的情况,可以自行引入下面的依赖。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>RELEASE</version>
    <scope>compile</scope>
</dependency>

接口测试

在把上面的四层架构都处理完之后,我们直接启动项目。

图示 11

可以预先在数据库中随机生成一些数据做测试用

图示 12

接下来在浏览器中访问 http://127.0.0.1:8087/doc.html 页面,该页面是 Swagger 生成的 API 文档然后经过 knife4j 美化过后的 API 文档页面。

图示 13

点击左侧的 “API接口” 可以看到出现了四个熟悉的接口,就是我们刚刚写的 “增删改查” 对应的接口,该 API 文档的好处就是可以在线对接口进行测试

添加接口测试

首先测试添加接口

依次点击并填写数据信息,然后点击发送

图示 14

看到相应内容 succeed 说明添加成功了

图示 15

我们前往 Navicat Premium 查看数据库内容有没有变化,刷新一下页面,可以看到在最下面的数据出现了我们刚刚添加进去的内容

图示 16

查询接口测试

其次测试查询接口

依次点击并填写 id 信息,然后点击发送

图示 17

可以看到响应内容成功拿到数据

图示 18

更新接口测试

然后测试更新接口

依次点击并填写信息,然后点击发送

图示 19

由于没有设置返回值,所以响应内容为空

我们直接去看数据库的内容变化,刷新一下数据库,可以看到该条数据已经发生了变化

图示 20

删除接口测试

最后测试删除接口

依次点击并填写 id 信息,然后点击发送

图示 21

由于没有设置返回值,所以还是直接前往数据库查看,刷新数据库,发现 id 为 54 的这条数据不见了,说明接口没问题

图示 22

经过测试,“增删改查”四个接口全部都能够正常使用。

实现项目功能接口

通过编写 “增删改查” 四个接口,相信大家都已经学会了 Spring Boot 的基础使用

接下来就可以编写我们本次的 “梦想屋” 小项目需要用到的一些接口了

为了节省时间,我们就直接在以上的程序基础上进行更改和添加功能,绝对不是因为我懒

model 层

model 层之前写的一个 Data 的实体类就继续保留,我们再增加一个 getUser 类,用来作为前端从接口获取到的信息的类

原理与 Data 类相同,这里就不再进行讲解了,直接上代码

@lombok.Data
@NoArgsConstructor
@AllArgsConstructor
@ApiModel("获取信息内容")
public class getUser {
    
    

    @ApiModelProperty(value = "信息所属ID", required = true, example = "1")
    private int id;

    @ApiModelProperty(value = "信息来源所属省份", required = true, example = "湖北")
    private String province;

    @ApiModelProperty(value = "梦想内容", required = true, example = "环游世界!")
    private String str;

    @ApiModelProperty(value = "点赞数", required = true, example = "52")
    private int likes;
}

mapper 层

mapper 层我们需要在 DataMappe r接口中添加新的内容

新增的方法有:

  • 查询点赞数前 50 名的信息
  • 查询最新的 50 条信息
  • 查询随机的 50 条信息
  • 更新指定 id 的点赞数 +1
  • 更新指定 id 的点赞数 -1

方法的原理还是与之前相同,只是改变了 sql 语句,sql 语句在这这里就不过多解释了,大家可以另作学习

    /**
     * 查询点赞数前50名的信息
     *
     * @return
     */
    @Results({
    
    
            @Result(property = "id", column = "id"),
            @Result(property = "province", column = "province"),
            @Result(property = "str", column = "str"),
            @Result(property = "likes", column = "likes")
    })
    @Select("select * from dream order by likes desc limit 50")
    List<getUser> findByLikes();

    /**
     * 查询最新的50条信息
     *
     * @return
     */
    @Results({
    
    
            @Result(property = "id", column = "id"),
            @Result(property = "province", column = "province"),
            @Result(property = "str", column = "str"),
            @Result(property = "likes", column = "likes")
    })
    @Select("select * from dream order by time desc limit 50")
    List<getUser> findByTime();

    /**
     * 查询随机的50条信息
     *
     * @return
     */
    @Results({
    
    
            @Result(property = "id", column = "id"),
            @Result(property = "province", column = "province"),
            @Result(property = "str", column = "str"),
            @Result(property = "likes", column = "likes")
    })
    @Select("select * from dream order by rand() limit 50")
    List<getUser> findByRand();

    /**
     * 更新指定id的点赞数+1
     *
     * @param id
     */
    @Update("update dream set likes=likes+1 where id=#{id}")
    void updateLikes(int id);

    /**
     * 更新指定id的点赞数-1
     *
     * @param id
     */
    @Update("update dream set likes=likes-1 where id=#{id}")
    void updateLikesJ(int id);

service 层

service 层在这里增加的内容比较多,因为是业务逻辑层,需要完成功能设计

首先在 service 目录下新建 getIP、getProvince、getTime 三个类

图示 23

分别用来获取用户 IP 地址、用户所在省份、内容发送时间

getIP 类代码如下,可以从用户的请求头中筛选出用户真实的IP地址

public class getIP {
    
    

    @Bean
    public String getIP_(HttpServletRequest request, HttpServletResponse response) {
    
    

        response.setContentType("text/html;charset=utf-8");

        /*设置响应头允许ajax跨域访问*/
        response.setHeader("Access-Control-Allow-Origin", "*");
        /* 星号表示所有的异域请求都可以接受, */
        
        response.setHeader("Access-Control-Allow-Methods", "GET,POST");

        return getIpAddr(request);
    }

    public String getIpAddr(HttpServletRequest request) {
    
    
        //获取请求头"x-forwarded-for"对应的value
        String ip = request.getHeader("x-forwarded-for");
        //如果获取的ip值为空
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    
    
            //则获取请求头"Proxy-Client-IP"对应的value
            ip = request.getHeader("Proxy-Client-IP");
        }
        //如果获取的ip值仍为空
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    
    
            //则获取请求头"WL-Proxy-Client-IP"对应的value
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        //如果以上方式获取的ip值都为空
        if(ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
    
    
            //则直接获取ip地址
            ip = request.getRemoteAddr();
        }
        //返回ip地址
        return ip;
    }
}

getProvince 类代码如下,可以调用外部 api 来检测出用户 ip 地址所属的省份

public class getProvince {
    
    

    public String getProvince_(String ip){
    
    
        //设置api的url
        String url = "https://ip.useragentinfo.com/json?ip=" + ip;

        RestTemplate template = new RestTemplate();

        //发起一个HTTP GET请求,获取指定URL的响应实体,String.class表示要获取的响应实体的类型是字符串类型。
        ResponseEntity<String> response =template.getForEntity(url,String.class);

        //将Spring类型转换为json类型
        JSONObject json = JSON.parseObject(response.getBody());
        //取出json中的数据
        String province = json.getString("province");

        return province;
    }
}

getTime 类代码如下,直接调用 Java 自带的包就行

public class getTime {
    
    

    public String getTime_(){
    
    

        //设置日期格式
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        // new Date()为获取当前系统时间,也可使用当前时间戳
        String date = df.format(new Date());

        return date;
    }
}

接下来修改 DataService 类的内容

需要新增以下方法

    /**
     * 获取用户数据并调用mapper层上传数据库
     * 
     * @param request
     * @param response
     * @param str
     * @return
     */
    public String Add(HttpServletRequest request, HttpServletResponse response, String str) {
    
    

        getIP getIP = new getIP();
        getProvince getProvince = new getProvince();
        getTime getTime = new getTime();

        //获取信息的IP地址
        String ip = getIP.getIP_(request, response);
        //ip="43.140.244.82";
        //获取信息所属省份
        String province = getProvince.getProvince_(ip);
        //获取当前时间
        String time = getTime.getTime_();
        //设置点赞数为0
        int likes = 0;

        //上传数据
        dataMapper.insert(ip, province, time, str, likes);

        return "succeed";
    }

    /**
     * 查询点赞数排名前50的信息
     *
     * @return
     */
    public List<getUser> findByLikes() {
    
    
        return dataMapper.findByLikes();
    }

    /**
     * 查询最新的50条信息
     *
     * @return
     */
    public List<getUser> findByTime() {
    
    
        return dataMapper.findByTime();
    }

    /**
     * 查询随机的50条信息
     *
     * @return
     */
    public List<getUser> findByRand() {
    
    
        return dataMapper.findByRand();
    }

    /**
     * 更新指定id对应的点赞数+1
     *
     * @param id
     * @return
     */
    public String updateLikes(int id) {
    
    
        dataMapper.updateLikes(id);

        return "succeed";
    }

    /**
     * 更新指定id对应的点赞数-1
     *
     * @param id
     * @return
     */
    public String updateLikesJ(int id) {
    
    
        dataMapper.updateLikesJ(id);

        return "succeed";
    }

controller 层

controller 层需要修改的内容不多

需要新增以下接口

    @ApiOperation("上传信息接口")
    @PostMapping("/Add/{str}")
    public String Add(HttpServletRequest request, HttpServletResponse response, @PathVariable String str) {
    
    

        return dataService.Add(request, response, str);
    }

    @ApiOperation("查询点赞数前50名的信息")
    @GetMapping("/findByLikes")
    public List<getUser> findByLikes() {
    
    

        return dataService.findByLikes();
    }

    @ApiOperation("查询最新的50条信息")
    @GetMapping("/findByTime")
    public List<getUser> findByTime() {
    
    

        return dataService.findByTime();
    }

    @ApiOperation("查询随机的50条信息")
    @GetMapping("/findByRand")
    public List<getUser> findByRand() {
    
    

        return dataService.findByRand();
    }

    @ApiOperation("更新指定id对应的点赞数+1")
    @PutMapping("/updateLikes/{id}")
    public String updateLikes(@PathVariable int id) {
    
    

        return dataService.updateLikes(id);
    }

    @ApiOperation("更新指定id对应的点赞数-1")
    @PutMapping("/updateLikesJ/{id}")
    public String updateLikesJ(@PathVariable int id) {
    
    

        return dataService.updateLikesJ(id);
    }

项目接口测试

启动项目

图示 24

访问 API 文档页面 http://127.0.0.1:8087/doc.html

可以看到我们编写的接口都在这里

由于是直接在前面程序的基础上改的,所以之前的“增删改查”接口也都还在

图示 25

根据前面测试“增删改查”接口相同的方法测试这些项目接口

接口全部正常使用

至此,我们的“梦想屋”小项目的后端接口已经编写完成

Vue 前端

安装 node

  • 官网下载安装:https://nodejs.org/zh-cn/
  • 管理员身份运行 Visual Studio Code
  • 按下 ctrl + ` 调出终端
  • 输入指令 node -v,显示版本号说明 node 安装好了
  • 输入指令 npm -v,显示版本号,说明 npm 可以正常使用

配置 npm 镜像

npm 默认的仓库地址在国外,访问速度较慢,我们切换成国内的淘宝镜像

输入指令安装

npm install -g cnpm --registry=https://registry.npm.taobao.org

输入指令 cnpm -v,显示版本号说明 cnpm 安装好了

注:

  • 管理员身份运行 Visual Studio Code
  • 输入指令: get-ExecutionPolicy 查询权限
    • Restricted:表示禁止终端使用命令
    • RemoteSigned:表示允许终端使用命令
  • 输入命令:set-ExecutionPolicy RemoteSigned 将权限改为允许使用
  • 输入指令:get-ExecutionPolicy 再次查询权限

创建 Vue 项目

安装脚手架

终端输入指令 cnpm i -g @vue/clinpm i -g @vue/cli全局安装

创建并配置项目

在任意位置新建一个文件夹用来放置项目

终端中通过 cd指令跳转到这个文件夹

输入指令 vue create dream创建项目

图示 26

  • 上下键:表示选择
  • 回车键:表示确认
配置方式

图示 27

选择 Manually select features手动配置

选择插件

选择需要安装的插件,如下勾选,按空格键选择

图示 28

  • Babel:解析 es6 转 es5 的插件
  • TypeScript:TypeScript插件
  • Progressive Web App (PWA) Support:渐进式Web应用程序(PWA)支持
  • Router:vue路由插件
  • Vuex:Vuex插件
  • CSS Pre-processors:css预处理插件
  • Linter/Formatter:格式化程序
  • Unit Testing:单元测试
  • E2E Testing:端到端(end-to-end)

回车确认

版本选择

图示 29

路由模式

选择是否为 history 模式,y 表示是,n 表示使用 hash 模式,这里选择的是 n

图示 30

  • history:利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法(需要特定浏览器支持)
  • hash: 浏览器 url 址栏中的 # 符号(如这个 URL:http://love.byzy.love/#/SuiJi ,hash 的值为“ #/SuiJi ”),hash 不被包括在 HTTP 请求中,所以对后端完全没有影响。因此改变 hash 不会重新加载页面,更容易进行打包上传服务器。
选择 CSS 预处理器

图示 31

选择编码规则

建议初学者选择第一项,表示只有报错时才会验证

图示 32

  • ESLint with error prevention only:只配置使用 ESLint 官网的推荐规则

  • ESLint + Airbnb config:官网推荐的规则 + Airbnb 第三方的配置

  • ESLint + Standard config:使用 ESLint 官网推荐的规则 + Standard 第三方的配置

  • ESLint + Prettier:使用 ESLint 官网推荐的规则 + Prettier 第三方的配置

检测条件

图示 33

  • Lint on save:保存就检测

  • Lint and fix on commit:fix 和 commit 时候检查

存放配置

图示 34

  • In dedicated config files:独立文件放置
  • In package.json:放 package.json 里
保存配置

保存配置并命名

图示 35

显示出如下界面说明配置完成

图示 36

输入指令 cd dream进入项目

项目结构

Visual Studio Code 打开项目文件夹,观察左侧项目目录

项目结构说明如下:

  • node_modules: 包含所有安装的npm包
  • public:包含所有不需要经过Webpack处理的静态文件,如index.html、favicon.ico等
    • favicon.ico
    • index.html
  • src:包含所有源代码
    • assets:存放静态资源文件,如图片、样式表等
      • logo.png
    • components:存放Vue组件
      • HelloWorld.vue
    • router:存放Vue路由
      • index.js
    • store:存放Vuex状态管理相关的代码
      • index.js
    • views:存放页面级别的Vue组件
      • AboutView.vue
      • HomeView.vue
    • App.vue:Vue应用程序的根组件
    • main.js:Vue应用程序的入口文件
  • .gitignore:Git忽略文件列表,告诉Git哪些文件和目录应该被忽略,不被纳入版本控制中。
  • babel.config.js:Babel配置文件,用于转换ES6+代码到ES5,以便它们能够在更旧的浏览器中运行。
  • jsconfig.json:用于配置VS Code的JavaScript语言服务的JSON配置文件。它可以用来设置JS项目的编译选项、路径别名、自动引入模块等选项。
  • package-lock.json:记录当前项目的依赖项及其版本信息,确保不同计算机或者环境下的依赖项保持一致。
  • package.json:Vue.js项目的元数据文件,包含项目的名称、版本、作者、依赖项等信息。其中,dependencies字段记录的是项目的运行时依赖项,而devDependencies字段记录的是项目的开发时依赖项。
  • README.md:项目的说明文档,描述项目的目的、功能、使用方法等等。
  • vue.config.js:Vue.js项目的配置文件,包含了一些Vue CLI的默认配置,可以用于自定义Webpack配置、开发环境的代理设置等等。使用vue-cli-service命令时,它将自动被加载并应用于项目配置。

Vue 组件结构

一个 Vue 组件分为三个部分,分别是:template 部分、script 部分、style 部分。

  • template

    • 组件的模板部分,用来定义组件的 html 结构。
    • 必须在里面方置一个 html 标签来包裹所有的代码,例如 <div></div>标签。
  • script

    • 组件的 JavaScript 代码部分,用来定义组件的逻辑和属性。
  • style

    • 组件的样式部分,它用来定义组件的样式。

Vue 组件调用与传值

我们就从官方的例子中来理解 Vue 组件间的传值与调用

由于HelloWorld.vue中的内容过多,所以我进行了删减

观察下面的两段 Vue 代码

//HomeView.vue

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js App"/>
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from '@/components/HelloWorld.vue'

export default {
  name: 'HomeView',
  components: {
    HelloWorld
  }
}
</script>
//HelloWorld.vue

<template>
  <div class="hello">
    <h1>{
   
   { msg }}</h1>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  props: {
    msg: String
  }
}
</script>

可以看到 HomeView.vue 组件中 template 部分有一个 HelloWorld 标签,这个就是我们自定义的 HelloWorld 组件,在这个标签中,我们将一串字符串赋值给 msg 传递了过去。

在 script 部分中,使用 ES6 的 import 语法引入了 “@/components/HelloWorld.vue” 文件,并将其赋值给 HelloWorld 变量。

  • export default:使用 ES6 的 export 语法导出一个默认的对象,该对象包含了组件的各种属性和方法。

  • name:定义了组件的名称,可以在代码中用来引用这个组件。

  • components:定义了组件所包含的子组件。在这里,我们将 HelloWorld 子组件注册为了 HomeView 组件的一个子组件,以便在 HomeView 组件的模板中使用 HelloWorld 组件。

接下来看 HelloWorld.vue 组件中的 script 部分,我们可以看到 props 属性,这个属性定义了该组件的数据属性,也就是它的输入。在这里,我们定义了一个名为 msg 的属性,它是一个字符串类型。该属性可以从组件外部传递进来,在组件内部使用。

然后在 template 部分, <h1>{ { msg }}</h1>调用了该值。

Vue 组件的生命周期

Vue组件的生命周期是指在组件实例化时,从开始到结束,不同阶段会自动执行的一些函数。Vue提供了一些钩子函数,让我们在这些生命周期阶段执行我们的自定义逻辑。Vue组件的生命周期可以分为以下三个阶段:

  1. 创建阶段:
    创建阶段包括组件实例化、数据观测、模板编译和挂载等过程,具体的生命周期函数有:
  • beforeCreate:在实例初始化之后,数据观测和事件配置之前被调用,此时data和methods等组件属性还未初始化。

  • created:在实例创建完成后被立即调用,此时 data 和 methods 等组件属性已经初始化,但是 DOM 节点还未挂载。

  • beforeMount:在挂载开始之前被调用,此时模板已经编译完成,但是还未渲染成 DOM。

  • mounted:在挂载完成后被调用,此时组件已经挂载到 DOM 上,可以进行 DOM 操作和异步数据请求等操作。

  1. 更新阶段:
    更新阶段包括数据更新和重新渲染等过程,具体的生命周期函数有:
  • beforeUpdate:在数据更新之前被调用,此时组件还未重新渲染。

  • updated:在数据更新之后被调用,此时组件已经重新渲染。

  1. 销毁阶段:
    销毁阶段包括组件销毁和清理等过程,具体的生命周期函数有:
  • beforeDestroy:在实例销毁之前被调用,此时组件还未销毁,可以进行一些清理工作。

  • destroyed:在实例销毁之后被调用,此时组件已经完全销毁,不再可用。

在组件的生命周期中,我们可以使用这些生命周期函数来执行一些初始化、清理和动态更新等操作。例如,在 created 生命周期函数中可以发起异步请求获取数据,在 beforeDestroy 生命周期函数中可以清理定时器或取消订阅等操作。

实现项目页面

在前面短暂的学习中,我们大致已经了解到了 Vue 的基本用法,接下来就能开始编写我们本次“梦想屋”项目的页面了。

页面框架搭建

我们需要理解本次项目的网页要做成什么样子

整理需求:

  • 一个导航栏
  • 三个页面
  • 页面中显示每个用户发出的内容
  • 可以给内容点赞
  • 可以发送内容

先把整体页面的框架模型搭建起来,然后再进行修饰

在了解了需求之后,就可以开始动手操作了

首先,把官方的示例文件给删除,或者在此基础上进行修改也可以

在 views 目录下新建 PaiHangView.vue、ZuiXinView.vue、SuiJiView.vue

在 components 目录下新建 UserList.vue、InputText.vue

图示 37

PaiHangView.vue、ZuiXinView.vue、SuiJiView.vue 三个组件作为三个页面,内容包括所有的 UserList.vue 组件排列起来。

UserList.vue 组件作为单个用户发送的内容,上面显示省份与点赞等内容。

InputText.vue 组件作为发送内容的组件,包括一个输入框和一个发送按钮。

项目配置

我们需要把项目运行端口更改一下,不然默认是8080端口

在 vue.config.js 文件中更改

const { defineConfig } = require('@vue/cli-service')
module.exports = defineConfig({
  transpileDependencies: true,

  devServer: {
    port: 8086, // 此处修改你想要的端口号
  },
  
});

然后在项目中我们会用到 Axios 以及 Element ,所以需要下载相关依赖并在 main.js 中引入

安装 Axios 指令:npm install axios

安装 Element 指令:npm install element-ui

引入 main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'

import ElementUI from 'element-ui';                      // 引入element-ui
import 'element-ui/lib/theme-chalk/index.css';           // element-ui的css样式要单独引入

import axios from 'axios'
Vue.prototype.$axios = axios

Vue.use(ElementUI);

Vue.config.productionTip = false

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

主页面设计

主页面的设计在根组件 App.vue 中进行,主要负责一些全局内容的显示

在 template 部分为一个标题,一个导航栏,一个 InputText 组件,一条每页都需要显示的提示文字。

<template>
  <div id="app">
    <h1 class="MyName">梦想屋</h1>
    <nav>
      <router-link to="/">排行</router-link> |
      <router-link to="/ZuiXin">最新</router-link> |
      <router-link to="/SuiJi">随机</router-link>
    </nav>
    <router-view/>
    <InputText/>
    <div class="newText">该列表仅显示50条内容!</div>
  </div>
</template>

在 script 部分引入 InputText.vue 组件

<script>
import InputText from '@/components/InputText.vue'

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

在 style 部分设置该组件的样式,以及一些全局效果

<style lang="scss">
#app {
    
    
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
}

.MyName{
    
    
  font-family:pyt; 
  font-size: 60px;
  background-clip: text;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-image: linear-gradient(to right, #41ffa9, rgba(79, 168, 252, 0.6));
  margin: 20px 10px 0px 10px;
  text-shadow: 3px 3px 3px rgba(0,0,0,0.2);//文本阴影
}

nav {
    
    
  padding: 20px;
  font-size: 30px;
  font-family:pyt; 
  text-shadow: 2px 2px 2px rgba(0,0,0,0.2);
  
  a {
    
    
    text-decoration: none;
    font-weight: bold;
    color: #2c3e50;

    &.router-link-exact-active {
    
    
      -webkit-tap-highlight-color: transparent; //清除蓝框
      text-decoration: none;
      color: #42b983;
    }
  }
}

body {
    
    
  background: rgb(244, 189, 255);
  background: linear-gradient(90deg, rgb(183, 180, 255) 0%, rgb(255, 255, 255) 50%, rgb(165, 240, 255) 100%);
}

//设置全局滚动条
body::-webkit-scrollbar {
    
    
  width: 5px;
  background-color: #F5F5F5;
}

body::-webkit-scrollbar-thumb {
    
    
  border-radius: 10px;
  -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, .3);
  background-color: #c4ffe4;
}

.newText{
    
    
  height: 80px;
  margin: 10px auto;
  font-size: 13px;
}

@font-face {
    
    
    font-family: pyt;        //自定义字体名称
    src:url("../src/assets/拼音体_百彦子烨.ttf");
    font-weight: normal;
    font-style: normal;
}
</style>

需要用到的一些图片以及字体文件放在 assets 目录下。

由于该组件用到的字体非常少,只有短短的 9 个字,所以我用开源的 Java 工具 sfntly 从大的字体包中提取出来要用的 9 个字,这样字体包就能缩小很多。

工具的使用我在这里就不讲解了,感兴趣的可以另外了解。

设置路由

在 router 目录下的 index.js 中设置路由

import Vue from 'vue'
import VueRouter from 'vue-router'
import PaiHangView from '../views/PaiHangView.vue'

Vue.use(VueRouter)

const routes = [
  {
    path: '/',
    name: 'PaiHang',
    component: PaiHangView
  },
  {
    path: '/ZuiXin',
    name: 'ZuiXin',
    // route level code-splitting
    // this generates a separate chunk (ZuiXin.[hash].js) for this route
    // which is lazy-loaded when the route is visited.
    component: () => import(/* webpackChunkName: "ZuiXin" */ '../views/ZuiXinView.vue')
  },
  {
    path: '/SuiJi',
    name: 'SuiJi',
    component: () => import('../views/SuiJiView.vue')
  }
]

const router = new VueRouter({
  routes
})

export default router

内容组件设计

template 部分如下,显示一个主要内容,在左下角显示省份,右下角显示点赞数和一个图片按钮。

<template>
  <div class="UserList">
    <div class="box">
      <div class="box1">
        <div class="str">{
   
   {str}}</div>
      </div>
      <div class="box2">
        <div class="province">{
   
   {province}}</div>
        <div class="likes">{
   
   {likes}}</div>
        <img :src="imagePath" @click="toggleImage" class="img-btn"/>
      </div>
    </div>
  </div>
</template>

script 部分如下,获取从上级页面传来的数据,在 created() 钩子函数中判断是否给这个内容点过赞,并将图片按钮初始化。

按钮绑定 toggleImage() 函数,通过点击按钮触发此函数,触发图片按钮的动作,以及更换图片,并且向服务器发出 put 请求,更新数据库中的数据,更新页面中显示的数据。

<script>
import axios from 'axios'

export default {
  name: 'UserList',
  props: {
    Obj: Object
  },
  data() {
    return {
      imagePath: require("@/assets/dianzhan.png"),
      str:this.Obj.str,
      province:this.Obj.province,
      likes:this.Obj.likes
    };
  },
  created() {
    if (localStorage.getItem(`isLiked${this.Obj.id}`)) {
      this.imagePath = require("@/assets/dianzhanyes.png");
    }
  },
  methods: {
    toggleImage() {
      //判断是否点赞过
      if (this.imagePath === require("@/assets/dianzhan.png")) {

        // 获取按钮元素
        const imgBtn = this.$el.querySelector('.img-btn');
        // 给按钮元素应用缩放和旋转的样式
        imgBtn.style.transform = 'scale(1.2) rotate(-10deg)';
        // 设置计时器,在 300 毫秒之后给按钮元素应用新的样式
        setTimeout(() => {
          imgBtn.style.transform = 'scale(1) rotate(10deg)';
          setTimeout(() => {
            imgBtn.style.transform = 'scale(1) rotate(0deg)';
          }, 300);
        }, 300);

        this.likes++;

        // 将图片替换为已点赞的图片
        this.imagePath = require("@/assets/dianzhanyes.png");
        // 将已点赞的状态存入 localStorage
        localStorage.setItem(`isLiked${this.Obj.id}`, true);

        //向服务器发送发送put请求
        axios.put(`http://(接口IP地址):8087/updateLikes/${this.Obj.id}`)
          .then(res => {

            console.log(res.data);

          })
          .catch(error => {
            console.log("点赞出错!")
            console.error(error);
          });

      } else {

        this.likes--;

        // 否则将图片替换为未点赞的图片
        this.imagePath = require("@/assets/dianzhan.png");
        // 并且删除已点赞的状态
        localStorage.removeItem(`isLiked${this.Obj.id}`);

        //向服务器发送发送put请求
        axios.put(`http://(接口IP地址):8087/updateLikesJ/${this.Obj.id}`)
          .then(res => {

            console.log(res.data);

          })
          .catch(error => {
            console.log("取消点赞出错!")
            console.error(error);
          });
      }
    }
  }

}
</script>

style 部分如下,设置对应的标签显示的位置,以及样式等。

<!-- 添加“scoped作用域”属性以将 CSS 限制为此组件 -->
<style scoped lang="scss">
.UserList{
    
    
  display: flex;
  justify-content: center;
  align-items: center;
}

.box {
    
    
  border: 1px solid #ddd;
  border-radius: 10px;
  box-shadow: 2px 2px 4px #ddd;
  padding: 10px;
  display: flex;
  flex-direction: column;//将 flex 子元素沿竖直方向排列
  align-items: center;//将 flex 子元素在纵轴上居中对齐
  margin: 5px;
  width: 500px;
  transition: all 0.3s ease;//面板放大
}

.box:hover {
    
    
    transform: scale(1.04);//面板放大
  }

.box1{
    
    
  width: 100%;
  float: left;
  margin: 5px;
}

.str {
    
    
  flex: 1;
  text-align: center;
  font-size: 20px;
  //font-family:wpyt; 
  width: 100%;
  height: 25px;
  display: flex;
  justify-content: center;
  align-items: center;
  text-shadow: 1px 1px 1px rgba(0,0,0,0.2);
}

.box2 {
    
    
  width: 100%;
  display: flex;
}

.province {
    
    
  text-align: center;
  font-size: 14px;
  width: 120px;
  height: 20px;
  display: flex;
}

.likes {
    
    
  text-align: center;
  font-size: 14px;
  width: 100px;
  height: 20px;
  margin: auto 5px auto auto;
  display: flex;
  justify-content: flex-end;
  align-items: center;
}

.img-btn {
    
    
  width: 16px;
  height: 16px;
  transition: all 0.3s ease;// 设置按钮元素的过渡效果
}
</style>

发送组件设计

template 部分如下,只有一个输入框以及一个图片按钮

<template>
    <div class="InputText">
        <input type="text" maxlength="15" v-model="inputValue" placeholder="发送你的梦想!" />
        <img src="../assets/fasong.png" class="submit-btn" />
    </div>
</template>

script 部分如下,通过点击按钮触发 submit() 函数,播放按钮的动画,以及获取输入框的内容,发送到服务器上。

<script>
import axios from 'axios'

export default {
    name: 'InputText',

    // 定义数据
    data() {
        return {
            isAnimating: false, // 用于标识动画是否正在播放
            inputValue: ''
        };
    },
    methods: {
        // 定义提交方法
        submit() {

            // 如果动画正在播放,直接返回
            if (this.isAnimating) return;

            //向服务器发送发送post请求
            axios.post(`http://(接口IP地址):8087/Add/${this.inputValue}`)
                .then(res => {

                    console.log(res.data);
                    this.inputValue = '';

                    this.$message({
                        message: '发送成功!',
                        type: 'success',
                        duration: 2000 // 自动关闭延迟时间,单位毫秒
                    });
                })
                .catch(error => {

                    this.$message({
                        message: '发送失败!',
                        type: 'error',
                        duration: 2000 // 自动关闭延迟时间,单位毫秒
                    });

                    console.log("发送信息出错!")
                    console.error(error);
                });

            this.isAnimating = true; // 标记动画正在播放
            // 获取提交按钮元素
            const btn = this.$el.querySelector(".submit-btn");
            // 记录提交按钮的原始 transform 值
            const originalTransform = btn.style.transform;
            // 设置提交按钮的 transform 值,使其向右上方移动
            btn.style.transform = "translate(100%, -100%)";

            // 设置动画播放完成后的回调函数
            setTimeout(() => {
                // 将提交按钮的 transform 值设置为原始值
                btn.style.transform = originalTransform;
                // 标记动画已经播放完毕
                this.isAnimating = false;
            }, 500);

        }
    },
    mounted() {
        // 在组件挂载完成后,给提交按钮元素绑定 click 事件
        this.$el.querySelector(".submit-btn").addEventListener("click", this.submit);
    }
};
</script>

style 部分如下,依然是设置整个发送组件的样式

<style scoped lang="scss">
.InputText {
    
    
    position: fixed;
    bottom: 10px;
    left: 0;
    right: 0;
    display: flex;
    justify-content: space-between;
    padding: 10px;
    background-color: transparent;
    width: 300px;
    margin: 0 auto;
    //设置 z-index 值为 999,显示为最顶部
    z-index: 999;
}

input[type="text"] {
    
    
    flex: 1;
    height: 40px;
    padding: 0 10px;
    background-color: rgba(255, 255, 255, 0.7);
    border: 1px solid transparent;
    border-radius: 20px;
    box-shadow: 0 0 10px #42b983;
    transition: all 0.3s ease-in-out;
}

input[type="text"]:hover {
    
    
    transform: scale(1.05);
    box-shadow: 0 0 15px rgba(24, 144, 255, 0.8);
}

.submit-btn {
    
    
    height: 40px;
    padding: 2px 0px 0px 10px;
    background-color: transparent;
    border: none;
    cursor: pointer;
    transform: scale(1);
    transition: all 0.3s ease-in-out;
    -webkit-tap-highlight-color: rgba(0, 0, 0, 0);
}

.submit-btn:hover {
    
    
    transform: scale(1.2);
}
</style>

分页面设计

三个分页面的作用是向数据库发出请求,获取到内容数据,再调用内容组件将各个内容排列显示在页面上

排行页面

template 部分如下,在该部分中只使用了一个自定义组件 UserList ,使用 Vue 的模板语法动态的生成一个组件列表。

v-for="(item, index) in Obj"指令表示对 Obj 对象进行遍历,生成对应数量的 <UserList>组件。

:key="index"属性用于标识每个组件的唯一性,使 Vue 能够更高效地管理组件的状态。

:Obj="item"属性将当前元素的值 item 传递给 <UserList>组件,作为组件的参数之一。

<template>
  <div class="PaiHang">
    <UserList v-for="(item, index) in Obj" :key="index" :Obj="item"/>
  </div>
</template>

script 部分如下,在该页面被初始化的的时候会调用 getList() 方法从服务器拿到数据,然后将json数据传入对应的 UserList 组件中。

<script>
// @ 是 /src 的别名
import UserList from '@/components/UserList.vue'
import axios from 'axios'

export default {
  name: 'PaiHangView',
  components: {
    UserList
  },

  data() {
    return {
      Obj: []
    }
  },

  //computed会缓存结果,methods每次都会重新计算
  methods:{
    getList(){
        let list = [];
        let newObjects = {};

        axios.get('http://(接口IP地址):8087/findByLikes')
          .then(res => {
            list=res.data;

            for(let i=0;i<list.length;i++){ 
              newObjects[i] = list[i]; 
            }
            console.log(newObjects);
            this.Obj = newObjects;
          })
          .catch(error => {

            this.$message({
              message: '获取页面内容失败!',
              type: 'error',
              duration: 2000
            });

            console.log("获取排行出错!")
            console.error(error);
          });
        
    }
    
  },

  created() {
    this.getList();
  }

}
</script>
最新页面

最新页面基本上与排行页面相同,唯一不同的地方就是请求的参数不同,所以只需要把请求的 url 改一下就行了,改成 API 定义的对应的接口

url:http://(接口IP地址):8087/findByTime

然后就是本组件的名字需要改一下

随机页面

同理,改一下请求的 url 就行

url:http://(接口IP地址):8087/findByRand

项目启动

在终端中输入指令

npm run serve

可以看到如下界面,说明项目成功运行

图示 38

根据提示访问本地地址 http://localhost:8086/

如果打开电脑的 8086 端口,那么通过手机访问局域网地址 http://192.168.0.104:8086/ 就可以在手机浏览器上看到手机端的页面了

我们用电脑上的浏览器访问看一下

图示 39

发现所有功能都正常使用,只是没有数据,是因为我们后端程序没有开启。

最后,将 index.html 文件中的网站名字,还有小图标修改一下,前端项目到这里就算完成了。

前后端结合

注:
没有服务器的可以直接在本地测试,在本地测试的话就不需要将前后端项目打包,只需要将项目内的 ip 地址改为本地的环回地址(127.0.0.1 或 localhost),然后直接运行前后端项目测试即可。

Spring Boot 后端项目打包

流程如下:

  1. 点击右侧的 Maven
  2. 选中 clean、compile、package
  3. 点击运行
  4. 等待打包完成,出现 build success 则表示打包成功
  5. 打包后的 jar 包在 target 目录下

图示 40

Vue 前端项目打包

流程如下:

  1. ctrl + ` 调出终端
  2. 输入指令 npm run build
  3. 等待打包完成
  4. 打包后的项目在 dist 目录下

图示 41

上传服务器

上传后端

这里使用 Termius 连接服务器,使用 SFTP 工具,在随便一个文件夹下上传 jar 包

然后输入指令在后台运行 jar 包,并且将日志输出 nohup.out 中

nohup java -jar server.jar &

上传前端

这里使用宝塔面板部署网站

  • 点击网站
  • 点击新建站点
  • 填写域名,选择根目录
  • 将打包好的文件全部上传至根目录

访问测试

PC 端浏览器访问效果如下

图示 42

经测试,点赞按钮和发送按钮动画正常播放,页面数据正常更新,梦想内容正常发布,各功能使用正常。

手机端浏览器访问效果如下

图示 43

页面正常排布,经测试,点赞按钮和发送按钮动画正常播放,页面数据正常更新,梦想内容正常发布,各功能使用正常。

至此,“梦想屋” 小项目成功完成。

项目示例:梦想屋

总结

本项目到这里也算是告一段落了,相信大家已经掌握了 Spring Boot + Vue 前后端分离项目的实现方法。由于我还是一个初学者,所以在项目中没有使用太高深的技术,而是采用了一些基础的方法。文章篇幅较长,可能会有一些细节部分没有讲得很清楚,因此只能大致概述整个过程。但我相信,读者们应该都掌握了相关的基础知识,所以都能够理解文章内容。如果你对某些细节还不太了解,可以给我留言或者自行查找相关资料。本文主要介绍了一些基础知识,如果文章中出现了技术性错误或其他问题,希望各位大佬能够给予指教,让我这个初学者能够更快地成长。非常感谢你们的关注和阅读!

猜你喜欢

转载自blog.csdn.net/BYZY1314/article/details/129057543