开发基础 | SpringBoot-Vue-Crud 练习项目

admin 2024年9月26日13:22:45评论11 views字数 29541阅读98分28秒阅读模式

SpringBoot-Vue-Crud

其中界面部分与前面的SSM项目基本一致. 这里我们再强调一下所使用的技术:

  1. 前端技术栈: Vue3 + Axios + ElementPlus
  2. 后端技术栈: SpringBoot + MyBatis Plus
  3. 数据库: Mysql
  4. 项目依赖管理: Maven
  5. 分页: MyBatis Plus 分页插件

环境搭建

前端项目创建

安装 node.js, 并且安装 node.js 的 npm, 用于管理前端项目包依赖. 具体可以参考我们之前的文章: https://mp.weixin.qq.com/s/SKwEvELP1_x-_bLkKQ_kpA笔者本地已经安装好node&&npm, 如下:

C:UsersAdministrator>node -vv14.17.3C:UsersAdministrator>npm -v6.14.13

随后进行创建springboot_vue项目:

C:UsersAdministratorIdeaProjectsspringboot_vue>vue create springboot_vueVue CLI v5.0.8? Please pick a preset:  Heihu_SSM ([Vue 3] babel, router, vuex)  MY_SSM ([Vue 3] babel, router, vuex)  SSM_REPEAT ([Vue 3] babel, router, vuex)  Default ([Vue 3] babel, eslint)  Default ([Vue 2] babel, eslint)> Manually select featuresVue CLI v5.0.8? Please pick a preset: Manually select features? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert selection, and<enter> to proceed) (*) Babel ( ) TypeScript ( ) Progressive Web App (PWA) Support (*) Router>(*) Vuex ( ) CSS Pre-processors ( ) Linter / Formatter ( ) Unit Testing ( ) E2E TestingVue CLI v5.0.8? Please pick a preset: Manually select features? Check the features needed for your project: Babel, Router, Vuex? Choose a version of Vue.js that you want to start the project with (Use arrow keys)> 3.x  2.x后面的选项依次选择: y package.json y n 即可.

最终安装完毕:开发基础 | SpringBoot-Vue-Crud 练习项目随后右键springboot_vue文件夹 -> 在 IDEA 中打开, 并且在IDEA中配置运行脚本, 如图:开发基础 | SpringBoot-Vue-Crud 练习项目最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目那么接下来我们安装一下ElementPlus:开发基础 | SpringBoot-Vue-Crud 练习项目当然了, 我们配置一下启动端口, 在vue.config.js文件中进行配置:

module.exports = {  devServer: {    port: 10000  }}

搭建项目基础页面

修改App.vue, 界面内容如下:

<template>  <div></div></template><style></style>

现在, 主页已经变为白板, 接下来我们修改/views/HomeView.vue文件内容如下:

<template>  <div><!--    <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>

我们已将多余的标签清空, 随后我们删掉components/HelloWorld.vue文件. 但创建components/Header.vue文件, 用于展示我们的导航栏头部信息.

<template>  <!-- 管理页面的头部分 -->  <div style="height: 50px;line-height: 50px;border-bottom: 1px solid #ccc; display: flex">    <div style="width: 200px;padding-left: 30px;font-weight: bold; color: dodgerblue">后台管理</div>    <div style="flex: 1"></div> <!-- 导航栏分成三部分, 前面200px, 后面100px, 中间填充 -->    <div style="width: 100px">下拉框</div>  </div></template><script>export default {  name: 'Header'}</script>

随后我们在App.vue中进行引入:

<template>  <div>    <Header/>  </div></template><style></style><script>import Header from "@/components/Header.vue";export default {  name: 'App',  components: {    Header  }}</script>

运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目

搭建全局 CSS 样式

随后创建assetscssglobal.css文件内容如下:

* {    padding: 0;    margin: 0;}

随后在main.js文件中进行引入:

import { createApp } from 'vue'import App from './App.vue'import router from './router'import store from './store'import "@/assets/css/global.css" // 引入 csscreateApp(App).use(store).use(router).mount('#app')

引入 ElementPlus

src/main.js文件中定义如下内容:

import { createApp } from 'vue'import App from './App.vue'import router from './router'import store from './store'import "@/assets/css/global.css" // 引入 cssimport ElementPlus from 'element-plus' // 引入 ElementPlusimport 'element-plus/dist/index.css' // 引入 ElementPlus 下的 CSS 文件createApp(App).use(store).use(router).use(ElementPlus).mount('#app')

最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目

引入下拉菜单

随后在srccomponentsHeader.vue文件中引入下拉菜单:

<template>  <!-- 管理页面的头部分 -->  <div style="height: 50px;line-height: 50px;border-bottom: 1px solid #ccc; display: flex">    <div style="width: 200px;padding-left: 30px;font-weight: bold; color: dodgerblue">后台管理</div>    <div style="flex: 1"></div> <!-- 导航栏分成三部分, 前面200px, 后面100px, 中间填充 -->    <div style="width: 100px">      <el-dropdown>    <span class="el-dropdown-link">      tom    </span>        <template #dropdown>          <el-dropdown-menu>            <el-dropdown-item>个人信息</el-dropdown-item>            <el-dropdown-item>退出登录</el-dropdown-item>          </el-dropdown-menu>        </template>      </el-dropdown>    </div>  </div></template><script>import {ArrowDown} from '@element-plus/icons-vue'export default {  name: 'Header'}</script><style scoped>.example-showcase .el-dropdown-link {  cursor: pointer;  color: var(--el-color-primary);  display: flex;  align-items: center;}</style>

创建侧边栏

创建srccomponentsAside.vue文件, 内容如下:

<script>export default {  name: "Aside" // 定义组件名称}</script><template>  <div>    <!-- 引入导航菜单 -->    <el-menu        default-active="2"        class="el-menu-vertical-demo"        @open="handleOpen"        @close="handleClose"    >      <el-sub-menu index="1">        <template #title>          <el-icon>            <location/>          </el-icon>          <span>Navigator One</span>        </template>        <el-menu-item-group title="Group One">          <el-menu-item index="1-1">item one</el-menu-item>          <el-menu-item index="1-2">item two</el-menu-item>        </el-menu-item-group>        <el-menu-item-group title="Group Two">          <el-menu-item index="1-3">item three</el-menu-item>        </el-menu-item-group>        <el-sub-menu index="1-4">          <template #title>item four</template>          <el-menu-item index="1-4-1">item one</el-menu-item>        </el-sub-menu>      </el-sub-menu>      <el-menu-item index="2">        <el-icon>          <icon-menu/>        </el-icon>        <span>Navigator Two</span>      </el-menu-item>      <el-menu-item index="3" disabled>        <el-icon>          <document/>        </el-icon>        <span>Navigator Three</span>      </el-menu-item>      <el-menu-item index="4">        <el-icon>          <setting/>        </el-icon>        <span>Navigator Four</span>      </el-menu-item>    </el-menu>  </div></template><style scoped></style>

随后在App.vue中进行引用:

<template>  <div>    <Header/>    <div style="display: flex"> <!-- 弹性部分 -->      <Aside/> <!-- 侧边栏 && 应用 -->      <router-view style="flex: 1"/> <!-- 这个部分通过路由展示, 通过URL来显示不同界面, 默认路由到 / -->    </div>  </div></template><style></style><script>import Header from "@/components/Header.vue";import Aside from "@/components/Aside.vue";export default {  name: 'App',  components: {    Header,    Aside  }}</script>

界面展示:开发基础 | SpringBoot-Vue-Crud 练习项目

创建表格

定位到srcviewsHomeView.vue, 并且定义内容如下:

<template>  <div>    <el-table :data="tableData" style="width: 100%"> <!-- :data 进行数据绑定, 我们需要在下面进行声明 tableData -->      <el-table-column sortable prop="date" label="Date" width="180"/> <!-- 加入 sortable 属性可以进行排序 -->      <el-table-column prop="name" label="Name" width="180"/>      <el-table-column prop="address" label="Address"/>    </el-table>  </div></template><script>export default {  name: 'HomeView',  components: {},  data() { // 声明该数据域    return {      tableData: [        {          date: '2016-05-03',          name: 'Tom',          address: 'No. 189, Grove St, Los Angeles',        },        {          date: '2016-05-02',          name: 'Tom',          address: 'No. 189, Grove St, Los Angeles',        },        {          date: '2016-05-04',          name: 'Tom',          address: 'No. 189, Grove St, Los Angeles',        },        {          date: '2016-05-01',          name: 'Tom',          address: 'No. 189, Grove St, Los Angeles',        }      ]    }  }}</script>

最终页面展示:开发基础 | SpringBoot-Vue-Crud 练习项目tableData清楚后:

<template>  <div>    <el-table :data="tableData" style="width: 100%"> <!-- :data 进行数据绑定, 我们需要在下面进行声明 tableData -->      <el-table-column sortable prop="date" label="Date" width="180"/> <!-- 加入 sortable 属性可以进行排序 -->      <el-table-column prop="name" label="Name" width="180"/>      <el-table-column prop="address" label="Address"/>    </el-table>  </div></template><script>export default {  name: 'HomeView',  components: {},  data() { // 声明该数据域    return {      tableData: [      ]    }  }}</script>

界面展示:开发基础 | SpringBoot-Vue-Crud 练习项目

增加操作按钮
<template>  <div>    <div style="margin: 10px 5px;"> <!-- 按钮部分 -->      <el-button type="primary">新增</el-button>      <el-button>其他</el-button>    </div>    <div style="margin: 10px 5px;"> <!-- 搜索部分 -->      <el-input v-model="input" style="width: 280px" placeholder="Please input" /> <!-- 输入框 -->      <el-button>搜索</el-button> <!-- 按钮 -->    </div>    <div>      <el-table :data="tableData" style="width: 100%"> <!-- 列表部分 -->        <el-table-column sortable prop="date" label="Date" width="180"/>        <el-table-column prop="name" label="Name" width="180"/>        <el-table-column prop="address" label="Address"/>        <el-table-column label="Operations"> <!-- 增加一列操作按钮 -->          <template #default="scope"> <!-- 后面再研究具体含义 -->            <el-button size="small" @click="handleEdit(scope.$index, scope.row)">              Edit            </el-button>            <el-button                size="small"                type="danger"                @click="handleDelete(scope.$index, scope.row)"            >              Delete            </el-button>          </template>        </el-table-column>      </el-table>    </div>  </div></template>

展示效果:开发基础 | SpringBoot-Vue-Crud 练习项目

ElementPlus 国际化

修改main.js, 内容如下:

import { createApp } from 'vue'import App from './App.vue'import router from './router'import store from './store'import "@/assets/css/global.css" // 引入 cssimport ElementPlus from 'element-plus' // 引入 ElementPlusimport 'element-plus/dist/index.css' // 引入 ElementPlus 下的 CSS 文件import zhCn from "element-plus/es/locale/lang/zh-cn" // 进行国际化createApp(App).use(store).use(router).use(ElementPlus, {    locale: zhCn // 国际化操作}).mount('#app')

可以看到从No Data变为了暂无数据:开发基础 | SpringBoot-Vue-Crud 练习项目

后端项目创建

创建基础项目

开发基础 | SpringBoot-Vue-Crud 练习项目

pom.xml 配置

定义为如下内容:

<parent> <!-- 引入基本的 parent -->    <groupId>org.springframework.boot</groupId>    <artifactId>spring-boot-starter-parent</artifactId>    <version>2.5.3</version></parent><dependencies>    <dependency> <!-- 从 parent 中拿 web 所需要的环境 -->        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-web</artifactId>    </dependency>    <dependency> <!-- 引入 lombok -->        <groupId>org.projectlombok</groupId>        <artifactId>lombok</artifactId>    </dependency>    <dependency> <!-- 引入数据库 JdbcTemplate 等信息, 并去 application.yml 中配置数据库链接项 -->        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-data-jdbc</artifactId>    </dependency>    <dependency> <!-- 引入 mybatis-plus -->        <groupId>com.baomidou</groupId>        <artifactId>mybatis-plus-boot-starter</artifactId>        <version>3.5.7</version>    </dependency>    <dependency> <!-- 引入 mysql 驱动, 默认 8.0 版本 -->        <groupId>mysql</groupId>        <artifactId>mysql-connector-java</artifactId>    </dependency>    <dependency> <!-- 引入 springboot 测试程序, 可以使用 @Resource 等注解信息 -->        <groupId>org.springframework.boot</groupId>        <artifactId>spring-boot-starter-test</artifactId>    </dependency>    <dependency> <!-- 不使用 Hikari 数据库连接池, 使用 DruidDataSource -->        <groupId>com.alibaba</groupId>        <artifactId>druid</artifactId>        <version>1.2.23</version>    </dependency></dependencies>

application.yml 配置

定义resource/application.yml文件内容如下:

server:  port: 80spring:  datasource:    url: "jdbc:mysql://localhost:3306/springboot_vue?useSSL=true&useUnicode=true&characterEncoding=utf-8"    username: "root"    password: "root"    driver-class-name: com.mysql.cj.jdbc.Driver

创建 SpringBoot 主程序

package com.heihu577;@SpringBootApplicationpublic class MainApp {    public static void main(String[] args) {        ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);    }}

最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目可以看到, SpringBoot已经成功启动了.

准备工具类 -> 用于返回 json

package com.heihu577.util;@Data@NoArgsConstructor@AllArgsConstructorpublic class Result<T> {    private String code; // 返回状态码    private String msg; // 对状态的说明    private T data; // 返回结果时, 携带的数据    public Result(T data) {        this.data = data; // 再多定义一个初始化 data 的方法    }    public static Result success() { // 定义一个 success 方法, 不存在任何额外信息        Result<Object> result = new Result<>();        result.setCode("200");        result.setMsg("success");        return result;    }    public static <T> Result<T> success(T data) { // 注意是静态泛型方法        Result<T> result = new Result<>(data); // 定义 success 方法, 携带额外信息        result.setCode("200");        result.setMsg("success");        return result;    }    public static Result fail(String code, String msg) {        // 错误原因多种多样, 所以这里可以做活一点        Result<Object> result = new Result<>();        result.setCode(code);        result.setMsg(msg);        return result;    }    public static <T> Result<T> fail(String code, String msg, T data) {        // 失败, 并返回一些特殊原因        Result<T> result = new Result<>(data);        result.setCode(code);        result.setMsg(msg);        return result;    }}

功能实现

创建数据库 && 数据表

DROP DATABASE IF EXISTS `springboot_vue`;CREATE DATABASE `springboot_vue` CHARACTER SET UTF8;USE `springboot_vue`CREATE TABLE `furn`( id int unsigned primary key auto_increment comment 'ID值', name varchar(64) not null comment '家具名', maker varchar(64) not null comment '商家', price decimal(11,2) not null comment '保留两位小数', sales int unsigned not null comment '销量', stock int unsigned not null comment '库存')charset utf8 engine innodb;INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock`)VALUES(NULL , '北欧风格小桌子' , '熊猫家居' , 180 , 666 , 7);INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock`)VALUES(NULL , '简约风格小椅子' , '熊猫家居' , 180 , 666 , 7);INSERT INTO furn(`id` , `name` , `maker` , `price` , `sales` , `stock`)VALUES(NULL , '典雅风格小台灯' , '蚂蚁家居' , 180 , 666 , 7);

准备对应的 JavaBean

创建完表后, 我们理所应当应该创建对应的JavaBean:

package com.heihu577.bean;@Data@TableName("furn") // 如果与表名不能映射, 可以使用 TableName 注解进行指定@NoArgsConstructor@AllArgsConstructorpublic class Furn {    /*    * +-------+------------------+------+-----+---------+----------------+    | Field | Type             | Null | Key | Default | Extra          |    +-------+------------------+------+-----+---------+----------------+    | id    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |    | name  | varchar(64)      | NO   |     | NULL    |                |    | maker | varchar(64)      | NO   |     | NULL    |                |    | price | decimal(11,2)    | NO   |     | NULL    |                |    | sales | int(10) unsigned | NO   |     | NULL    |                |    | stock | int(10) unsigned | NO   |     | NULL    |                |    +-------+------------------+------+-----+---------+----------------+    * */    private Integer id;    private String name;    private String maker;    private BigDecimal price;    private Integer sales;    private Integer stock;}

准备 Mapper 层

有了Bean, 当然要准备对应的Mapper:

package com.heihu577.mapper;public interface FurnMapper extends BaseMapper<Furn> {    // 在 MyBatis 中继承父接口, 如果有额外的方法在该接口中定义.}

定义完毕之后, 我们在主类进行扫描:

@MapperScan("com.heihu577.mapper")@SpringBootApplicationpublic class MainApp {    public static void main(String[] args) {        ConfigurableApplicationContext ioc = SpringApplication.run(MainApp.class, args);    }}
测试结果
package com.heihu577.test;@org.springframework.boot.test.context.SpringBootTestpublic class SpringBootTest {    @Resource    private FurnMapper furnMapper;    @Test    public void t1() {        Furn furn = furnMapper.selectById(1);        System.out.println(furn);        /*        2024-08-22 09:35:38.942  INFO 11888 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Starting...        2024-08-22 09:35:40.467  INFO 11888 --- [           main] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Start completed.        2024-08-22 09:35:40.715  INFO 11888 --- [           main] com.heihu577.test.SpringBootTest         : Started SpringBootTest in 4.951 seconds (JVM running for 5.687)        Furn(id=1, name=北欧风格小桌子, maker=熊猫家居, price=180.00, sales=666, stock=7)        2024-08-22 09:35:41.042  INFO 11888 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown initiated...        2024-08-22 09:35:41.048  INFO 11888 --- [ionShutdownHook] com.zaxxer.hikari.HikariDataSource       : HikariPool-1 - Shutdown completed.        */    }}

准备 Service 层

package com.heihu577.service;import com.baomidou.mybatisplus.extension.service.IService;import com.heihu577.bean.Furn;public interface FurnService extends IService<Furn> {}

进行实现:

package com.heihu577.service.impl;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.heihu577.bean.Furn;import com.heihu577.mapper.FurnMapper;import com.heihu577.service.FurnService;@Servicepublic class FurnServiceImpl extends ServiceImpl<FurnMapper, Furn> implements FurnService {}
测试结果
@org.springframework.boot.test.context.SpringBootTestpublic class SpringBootTest {    @Resource    private FurnMapper furnMapper;    @Resource    private FurnService furnService;    @Test    public void t1() {        Furn furn = furnMapper.selectById(1);        System.out.println(furn);    }    @Test    public void t2() {        List<Furn> list = furnService.list();        System.out.println(list);        // [Furn(id=1, name=北欧风格小桌子, maker=熊猫家居, price=180.00, sales=666, stock=7), Furn(id=2, name=简约风格小椅子, maker=熊猫家居, price=180.00, sales=666, stock=7), Furn(id=3, name=典雅风格小台灯, maker=蚂蚁家居, price=180.00, sales=666, stock=7)]    }}

Hikari 改为 Druid

定义如下配置类即可, 原理不再描述:

@Configurationpublic class DruidDataSourceAutoConfiguration {    @Bean    @ConfigurationProperties("spring.datasource")    public DataSource dataSource() {        DruidDataSource druidDataSource = new DruidDataSource();        return druidDataSource;    }}

添加家具

完成思路:

  1. 后台代码从 mapper -> service -> controller, 并对每层代码进行测试, 到 controller 这一层, 使用 Postman 发送 http 请求完成测试.
  2. 完成前台代码, 使用 axios 发送 json 数据给后台, 实现添加家具信息功能.

准备 Controller 层

在之前, 我们已经准备好Mapper层, Service层了, 所以在这里我们需要准备Controller层, 代码如下:

@RestController // 返回 Json@Slf4jpublic class FurnController {    @Resource    private FurnService furnService;    @PostMapping("/save")    public Result save(@RequestBody Furn furn) { // 接收 Json, 如果是表单提交的, 那么不需要使用 RequestBody 注解.        log.info("furn = {}", furn);        boolean res = furnService.save(furn);        return res ? Result.success() : Result.fail("400", "添加失败!");    }}

测试结果:开发基础 | SpringBoot-Vue-Crud 练习项目

解决一个自增长问题

在这里, 如果我们的JSON中并没有指定ID, 按道理应该根据MYSQL-AUTO_INCREMENT进行自增长, 但是这里并没有, 如图:开发基础 | SpringBoot-Vue-Crud 练习项目

这里我们可以使用@TableId注解, 放到id字段上, 我们看一下TableId注解的声明:

public @interface TableId {    String value() default "";    IdType type() default IdType.NONE;}

这里的IdType我们可以看一下源码信息:

public enum IdType {    AUTO(0), // 这里是自增长    NONE(1),    INPUT(2),    ASSIGN_ID(3),    ASSIGN_UUID(4);}

所以我们需要这样指定:

public class Furn {    /*    * +-------+------------------+------+-----+---------+----------------+    | Field | Type             | Null | Key | Default | Extra          |    +-------+------------------+------+-----+---------+----------------+    | id    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |    | name  | varchar(64)      | NO   |     | NULL    |                |    | maker | varchar(64)      | NO   |     | NULL    |                |    | price | decimal(11,2)    | NO   |     | NULL    |                |    | sales | int(10) unsigned | NO   |     | NULL    |                |    | stock | int(10) unsigned | NO   |     | NULL    |                |    +-------+------------------+------+-----+---------+----------------+    * */    @TableId(type = IdType.AUTO)    private Integer id;    private String name;    private String maker;    private BigDecimal price;    private Integer sales;    private Integer stock;}

修改完毕后测试:开发基础 | SpringBoot-Vue-Crud 练习项目

前端界面处理

安装 axios

在项目目录下, 进行安装axios:

PS C:UsersAdministratorIdeaProjectsspringboot_vuespringboot_vue> npm install axios -S
单机添加按钮出现弹框

参考: http://element-plus.org/zh-CN/component/dialog.html准备如下代码:

<el-dialog v-model="dialogFormVisible" title="提示" width="500">  <el-form :model="furnObj">    <el-form-item label="名称">      <el-input v-model="furnObj.name" autocomplete="off" />    </el-form-item>    <el-form-item label="商家">      <el-input v-model="furnObj.maker" autocomplete="off" />    </el-form-item>    <el-form-item label="价格">      <el-input v-model="furnObj.price" autocomplete="off" />    </el-form-item>    <el-form-item label="销量">      <el-input v-model="furnObj.sales" autocomplete="off" />    </el-form-item>    <el-form-item label="库存">      <el-input v-model="furnObj.stock" autocomplete="off" />    </el-form-item>  </el-form>  <template #footer>    <div class="dialog-footer">      <el-button @click="dialogFormVisible = false; this.furnObj = {};">Cancel</el-button>      <el-button type="primary" @click="addFurn">        Confirm      </el-button>    </div>  </template></el-dialog>

并且在数据区准备如下内容:

export default {  name: 'HomeView',  components: {},  data() { // 声明该数据域    return {      furnObj: {},      dialogFormVisible: false,      tableData: []    }  },  methods: {    addFurn() {    }  }}
定义 request.js

我们定义一个utils/request.js, 用于在我们的axios发送数据前, 发送数据后增加interceptor, 方便我们发送JSON.

import axios from "axios"; // 引入 axiosconst request = axios.create({    timeout: 5000, // 设置超时时间, 如果发送请求超过五秒没有响应, 就超时.})// 定义 request 拦截器, 在发送请求前, 进行设置 Content-Typerequest.interceptors.request.use(config => {    config.headers['Content-Type'] = 'application/json; charset=utf-8'; // 设置请求头    return config;}, error => {    return Promise.reject(error) // 出现错误就不继续了})// response 拦截器, 在调用接口响应后, 统一的处理返回结果request.interceptors.response.use(response => {    let res = response.data    if (response.config.responseType == 'blob') {        return res; // 结果是文件, 直接返回     }    if (typeof res === 'string') { // 如果是 string, 就转换成 json 对象        res = res ? JSON.parse(res) : res;// 如果 res 不为 null, 就进行转换      }    return res}, error => {    console.log("error", error)    return Promise.reject(error);})export default request // 将组件导出, 其他模块可以进行使用
跨域问题解决

当然了, 我们使用axios发送数据之前, 肯定要解决一下跨域问题的, 在vue.config.js文件中进行添加:

const {defineConfig} = require('@vue/cli-service')module.exports = {    devServer: {        port: 10000, proxy: { // 设置代理            '/api': { // 设置拦截器, 拦截格式                target: 'http://localhost/', // 代理的目标地址                changeOrigin: true, // 是否设置同源                pathRewrite: { // 路径重写                    '/api': '' // 选择忽略拦截器里面的单词                }            }        }    }}

定义完配置信息后一定要重启Vue服务器.

addFurn 方法完善
methods: {addFurn() {  request.post("/api/save", this.furnObj).then((res) => {    if (res['code'] == '200') {      ElMessage({        message: '添加家具成功~',        type: 'success',      });    } else {      // 失败的弹窗    }    this.furnObj = {}    this.dialogFormVisible = false;  });}}

最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目

显示家具信息

访问首页时, 表单内存在信息, 并展示出来:开发基础 | SpringBoot-Vue-Crud 练习项目该功能比较简单, 因为是Mybatis Plus环境, 所以在这里我们只需要定义一个Controller, 在Vuecreated钩子函数中进行发送请求, 并将数据绑定到表单中即可.

开发 Controller

定义如下方法:

@RequestMapping("/furns")public Result<List<Furn>> furns() {    Result<List<Furn>> result = Result.success(furnService.list());    return result;}

查看结果:开发基础 | SpringBoot-Vue-Crud 练习项目

前端显示家具列表

修改表格信息如下:

  <el-table :data="tableData" style="width: 100%"> <!-- 列表部分 -->    <el-table-column prop="id" label="id" width="180"/>    <el-table-column prop="name" label="Name" width="180"/>    <el-table-column prop="maker" label="Maker" width="180"/>    <el-table-column prop="price" label="Price" width="180"/>    <el-table-column prop="sales" label="sales"/>    <el-table-column prop="stock" label="stock"/>    <el-table-column label="Operations"> <!-- 增加一列操作按钮 -->      <template #default="scope"> <!-- 后面再研究具体含义 -->        <el-button size="small" @click="handleEdit(scope.$index, scope.row)">          Edit        </el-button>        <el-button            size="small"            type="danger"            @click="handleDelete(scope.$index, scope.row)"        >          Delete        </el-button>      </template>    </el-table-column>  </el-table>

定义好这么多字段之后, 我们可以在created方法中进行发送数据, 并配置进来:

  methods: {    list(){      request.get("/api/furns").then(res => {        this.tableData = res['data'];      });    }      // ... 其他方方法  },  created() {    this.list();  }}

最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目当然, 当我们添加物品成功后, 也调用一下list方法, 对表单数据进行刷新:

addFurn() {  request.post("/api/save", this.furnObj).then((res) => {    if (res['code'] == '200') {      ElMessage({        message: '添加家具成功~',        type: 'success',      });    } else {      // 失败的弹窗    }    this.furnObj = {}    this.dialogFormVisible = false;    this.list();  });},

修改家具

修改家具这个功能模块, 是当我们单机Edit按钮时, 弹出修改框给我们操作.开发基础 | SpringBoot-Vue-Crud 练习项目当然了, 我们使用的修改框还是之前ElementPlus引用的添加框, 他两个的表单是一样的.

开发 Controller

@PutMapping("/update")public Result update(@RequestBody Furn furn) {    furnService.updateById(furn); // MyBatisPlus 的 updateById 方法    return Result.success();}

当然了, 使用PutMapping, 转化为 HTTP 请求的话, 我们需要配置hiddenmethod, 在application.yml文件中进行配置:

server:  port: 80spring:  datasource:    url: "jdbc:mysql://localhost:3306/springboot_vue?useSSL=true&useUnicode=true&characterEncoding=utf-8"    username: "root"    password: "root"    driver-class-name: com.mysql.cj.jdbc.Driver  mvc:    hiddenmethod:      filter:        enabled: true

但是如果使用Vue的话, Vueaxios提供了发送put请求的功能, 所以在这里不配置也是可以的.

前端修改功能增加

因为Edit按钮绑定了handleEdit方法, 如下:

<el-button size="small" @click="handleEdit(scope.$index, scope.row)">  Edit</el-button>

所以我们在这里定义handleEdit方法内容如下:

handleEdit(index, row) {  let obj = JSON.parse(JSON.stringify(row)); // 拿到该行数据  this.furnObj = obj;  this.dialogFormVisible = true;}

当然了, 显示表单框后, 因为用的同一个表单框, 添加修改唯一的区别就在于, 添加是不存在id的, 但修改存在具体id, 所以我们可以根据此情况, 修改addFurn方法的逻辑如下:

addFurn() {  if (this.furnObj.id) { // 修改操作    request.put("/api/update", this.furnObj).then((res) => {      if (res['code'] == '200') {        ElMessage({          message: '修改家具成功~',          type: 'success',        });      } else {        // 失败的弹窗      }      this.furnObj = {}      this.dialogFormVisible = false;      this.list();    });  } else { // 添加操作    request.post("/api/save", this.furnObj).then((res) => {      if (res['code'] == '200') {        ElMessage({          message: '添加家具成功~',          type: 'success',        });      } else {        // 失败的弹窗      }      this.furnObj = {}      this.dialogFormVisible = false;      this.list();    });  }}

前端修改功能添加 (思路2)

上面的思路则是, 我们将本行数据丢给handleEdit方法进行处理, 下面我们使用思路二完成这个需求. 根据本行数据中的ID号, 去SpringBoot查询出具体ID的数据, 然后渲染给表单.

首先修改handleEdit方法为handleEdit2方法, 如下:

<el-button size="small" @click="handleEdit2(scope.row.id)">  Edit</el-button>

随后我们在Controller中定义一个根据ID返回数据的方法:

@GetMapping("/furn/{id}")public Result<Furn> furnById(@PathVariable("id") Integer id) {    Furn furn = furnService.getById(id);    return Result.success(furn);}

最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目随后在Vue中进行实现handleEdit2方法即可:

handleEdit2(id) {  request.get(`/api/furn/${id}`).then((res) => {    if(res['code'] == '200'){      this.furnObj = res['data'];      this.dialogFormVisible = true;    }  });}

随后即可正常运行.

删除家具

对如下功能的一个完善:开发基础 | SpringBoot-Vue-Crud 练习项目并且弹出确认删除吗确认框.

开发 Contoller

定义如下Controller:

@DeleteMapping("/delete/{id}")public Result delete(@PathVariable("id") Integer id) {    furnService.removeById(id);    return Result.success();}

前端气泡确认框

在http://element-plus.org/zh-CN/component/popconfirm.html中拷贝到我们的项目中即可.

<el-popconfirm title="Are you sure to delete this?" @confirm="handleDelete(scope.$index, scope.row)">  <template #reference>    <el-button        size="small"        type="danger"    >      Delete    </el-button>  </template></el-popconfirm>

并定义handleDelete方法如下:

handleDelete(index, row) {  let myObj = JSON.parse(JSON.stringify(row));  request.delete('/api/delete/' + myObj['id']).then((res) => {    if (res['code'] == '200') {      ElMessage({        message: '删除家具成功~',        type: 'success',      });    }    this.list();  });},

分页查询

在之前我们SSM项目时, 完成过该功能, 可以使用MyBatis的分页插件, 但是当前我们的场景在MyBatis-Plus下, 现在我们看看如何完成这个项目.

开发 Controller

使用MyBatis-Plus插件, 可以参考官网: https://www.baomidou.com/plugins/pagination/定义如下配置类:

@Configurationpublic class MyBatisPlusPluginAutoConfiguration {    @Bean    public MybatisPlusInterceptor mybatisPlusInterceptor() {        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); // 如果配置多个插件, 切记分页最后添加        // 如果有多数据源可以不配具体类型, 否则都建议配上具体的 DbType        return interceptor;    }}

随后我们定义如下Controller进行应用:

@GetMapping("/furnsByPage")public Result listFurnsByPage(@RequestParam(defaultValue = "1") Integer pageNum,                              @RequestParam(defaultValue = "5") Integer pageSize) {    // pageNum: 当前第几页 pageSize: 当前页面大小    Page<Furn> page = furnService.page(new Page<>(pageNum, pageSize));    return Result.success(page);}

配置mybatis-plus日志信息, 方便我们进行观察SQL语句, 配置在application.yml文件中:

mybatis-plus:  configuration:    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目

前端分页导航

根据ElementPlus官网: http://element-plus.org/zh-CN/component/pagination.html我们使用如下分页:

<el-pagination    v-model:current-page="currentPage2"    v-model:page-size="pageSize2"    :page-sizes="[100, 200, 300, 400]"    layout="sizes, prev, pager, next"    :total="total"    @size-change="handleSizeChange"    @current-change="handleCurrentChange"/>

数据区如下内容:

return {  currentPage2: 1, // 当前第几页  pageSize2: 2, // 每页大小  total: 0, // 总共多少条记录, 还不知道, 等服务器端返回结果  furnObj: {},  dialogFormVisible: false,  tableData: []}

所以我们在created方法调用的list方法中应该改为如下代码:

list() {  // request.get("/api/furns").then(res => {  //   this.tableData = res['data'];  // }); 修改为如下代码  request.get("/api/furnsByPage", {    params: {      pageNum: this.currentPage2,      pageSize: this.pageSize2    }  }).then(res => {    this.total = res['data']['total'];    this.tableData = res['data']['records'];  });},

并且提供handleSizeChange方法以及handleCurrentChange方法:

handleSizeChange(dstSize) {  this.pageSize2 = dstSize;  this.list(); // 修改成功后, 刷新, 重新渲染},handleCurrentChange(dstCurrentPage) {  this.currentPage2 = dstCurrentPage;  this.list();},

带条件分页查询显示

这里的问题主要是在我们的SQL语句中如何增加条件.

开发 Controller

定义如下方法:

@GetMapping("/furnsByPageAndCondition")public Result listFurnsByPageCondition(@RequestParam(defaultValue = "1") Integer pageNum,                                       @RequestParam(defaultValue = "5") Integer pageSize,                                       @RequestParam(defaultValue = "") String search) {    QueryWrapper<Furn> furnCondition = Wrappers.query(); // 创建一个条件表达式对象, 可以放入些许条件    if (StringUtils.hasText(search)) {        furnCondition.like("name", search); // name LIKE 值    }    // pageNum: 当前第几页 pageSize: 当前页面大小    // 放入 furnCondition, 用于增加条件    Page<Furn> page = furnService.page(new Page<>(pageNum, pageSize), furnCondition);    return Result.success(page);}

定义完毕后, 我们就可以进行我们的方法查询了.

LambdaQueryWrapper 使用

除了使用上面这种方式, 我们也可以使用LambdaQueryWrapper进行封装条件, 定义方法如下:

@GetMapping("/furnsByPageAndCondition2")public Result listFurnsByPageCondition2(@RequestParam(defaultValue = "1") Integer pageNum,                                        @RequestParam(defaultValue = "5") Integer pageSize,                                        @RequestParam(defaultValue = "") String search) {    LambdaQueryWrapper<Furn> lambdaQueryWrapper = Wrappers.lambdaQuery();    if (StringUtils.hasText(search)) {        lambdaQueryWrapper.like(Furn::getName, search); // Furn::getName 可以通过 JAVABEAN 的 name 字段, 来得到数据库中的字段名称    }    Page<Furn> page = furnService.page(new Page<>(pageNum, pageSize), lambdaQueryWrapper);    return Result.success(page);}

最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目

lambda 表达式使用解释

其实Java中的lambda表达式类似于PHP中的回调函数, 案例如下:

package com.heihu577;public class MyTester {    public static void main(String[] args) {        MyFunction<Dog, String> getNameFunction = Dog::getName; // 得到回调函数, 只是调用方式遵循了 MyFunction 接口        Dog dog = new Dog();        dog.name = "大黄狗";        String callRes = getNameFunction.call(dog); // 调用回调函数, 返回结果        System.out.println(getNameFunction); // com.heihu577.MyTester$$Lambda$1/1834188994@bebdb06        System.out.println(callRes); // 返回大黄狗    }}@FunctionalInterface // 函数接口定义后, 只允许定义一个抽象方法, 为了确保指向了 Dog::getName 方法.interface MyFunction<T, R> {    R call(T t); // 传入 T 对象, 返回 R 结果}class Dog {    public String name;    public String getName() {        return name;    }}

前端处理

我们看一下我们的表单双向绑定的哪个变量, 并且定义它:

<div style="margin: 10px 5px;"> <!-- 搜索部分 -->  <el-input v-model="input" style="width: 280px" placeholder="Please input"/> <!-- 输入框 -->  <el-button @click="this.list()">搜索</el-button> <!-- 按钮, 注意增加 @click 时间 --></div>...data() { // 声明该数据域    return {      input: '', // 在数据区中双向绑定该变量

绑定完毕之后, 我们修改list方法如下:

list() {  // request.get("/api/furns").then(res => {  //   this.tableData = res['data'];  // }); 修改为如下代码  request.get("/api/furnsByPageAndCondition", { // 修改为我们刚刚定义的 Controller    params: {      pageNum: this.currentPage2,      pageSize: this.pageSize2,      search: this.input // 进行查询我们的 search 内容    }  }).then(res => {    this.total = res['data']['total'];    this.tableData = res['data']['records'];  });},

最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目

前端校验

这里主要是参考ElementPlus官网的API, 参考: https://element-plus.youqua.cn/zh-CN/component/form.html#%E8%A1%A8%E5%8D%95%E6%A0%A1%E9%AA%8C

表格样式

准备如下vue:

<el-dialog v-model="dialogFormVisible" title="提示" width="500">  <el-form :rules="rules" ref="form" :model="furnObj"> <!-- 定义 rules -->    <el-form-item prop="name" label="名称"> <!-- 使用 prop 应用对应 rule 规则 -->      <el-input v-model="furnObj.name" autocomplete="off"/>    </el-form-item>    <el-form-item prop="maker" label="商家">      <el-input v-model="furnObj.maker" autocomplete="off"/>    </el-form-item>    ...    </el-form></el-dialog>

数据区定义rules:

rules: {    name: [      { required: true, message: '请输入名称', trigger: 'blur' },      { min: 3, max: 5, message: '名称3到5之间', trigger: 'blur' },    ],    maker: [      { required: true, message: '必须填写商家名', trigger: 'blur' }    ],    price: [      { required: true, message: '必须填写价格', trigger: 'blur' }    ],    sales: [      { required: true, message: '必须填写销量', trigger: 'blur' }    ],    stock: [      { required: true, message: '必须填写库存', trigger: 'blur' }    ]}

定义完毕后我们可以看到效果:开发基础 | SpringBoot-Vue-Crud 练习项目随后我们在addFurn方法中进行定义事件:

addFurn() {  if (this.furnObj.id) {    request.put("/api/update", this.furnObj).then((res) => {      if (res['code'] == '200') {        ElMessage({          message: '修改家具成功~',          type: 'success',        });      } else {        // 失败的弹窗      }      this.furnObj = {}      this.dialogFormVisible = false;      this.list();    });  } else {    this.$refs['form'].validate((valid) => {      if (valid) {        request.post("/api/save", this.furnObj).then((res) => {          if (res['code'] == '200') {            ElMessage({              message: '添加家具成功~',              type: 'success',            });          } else {            // 失败的弹窗          }          this.furnObj = {}          this.dialogFormVisible = false;          this.list();        });      } else {        ElMessage({          message: '表单没有通过, 不提交!',          type: 'error',        });        return false;      }    });  }},

但是当上次校验不通过后, 提示信息会驻留在这个表单上, 所以我们需要清除上次出错的消息, 如下:

<div class="dialog-footer">  <el-button @click="dialogFormVisible = false; this.furnObj = {};this.$refs['form'].resetFields();">Cancel</el-button> <!-- 清空上次消息 -->  <el-button type="primary" @click="addFurn">    Confirm  </el-button>

后端校验

除了前端校验, 我们也需要编写后端校验, 否则攻击者使用BP即可添加非法数据.

pom.xml文件中引入hibernate-validator.jar文件:

<dependency>    <groupId>org.hibernate</groupId>    <artifactId>hibernate-validator</artifactId>    <version>5.0.0.CR2</version></dependency>

随后我们在具体BEAN的字段上使用注解进行修饰即可:

@Data@TableName("furn") // 如果与表名不能映射, 可以使用 TableName 注解进行指定@NoArgsConstructor@AllArgsConstructorpublic class Furn {    /*    * +-------+------------------+------+-----+---------+----------------+    | Field | Type             | Null | Key | Default | Extra          |    +-------+------------------+------+-----+---------+----------------+    | id    | int(10) unsigned | NO   | PRI | NULL    | auto_increment |    | name  | varchar(64)      | NO   |     | NULL    |                |    | maker | varchar(64)      | NO   |     | NULL    |                |    | price | decimal(11,2)    | NO   |     | NULL    |                |    | sales | int(10) unsigned | NO   |     | NULL    |                |    | stock | int(10) unsigned | NO   |     | NULL    |                |    +-------+------------------+------+-----+---------+----------------+    * */    @TableId(type = IdType.AUTO)    private Integer id;    @NotEmpty(message = "请输入家具名称")    @Length(min = 3, max = 5)    private String name;    @NotEmpty(message = "请输入商家名")    private String maker;    @NotNull(message = "请输入数字")    @Range(min = 0, message = "价格不能小于0")    private BigDecimal price;    @NotNull(message = "请输入销量")    @Range(min = 0, message = "销量不能小于0")    private Integer sales;    @NotNull(message = "请输入")    @Range(min = 0, message = "库存不能小于0")    private Integer stock;}

随后在控制器中, 进行判断.

@PostMapping("/save")public Result save(@Validated @RequestBody Furn furn, Errors errors) { // 接收 Json    log.info("furn = {}", furn);    if (errors.hasErrors()) {        Map<String, String> errorMap = new ConcurrentHashMap<>();        List<FieldError> fieldErrors = errors.getFieldErrors();        for (FieldError fieldError : fieldErrors) {            errorMap.put(fieldError.getField(), fieldError.getDefaultMessage());        }        return Result.fail("400", "添加失败!", errorMap);    } else {        boolean res = furnService.save(furn);        return Result.success();    }}

最终运行结果:开发基础 | SpringBoot-Vue-Crud 练习项目最后如何处理可以根据前端拿来json, 或者后端修改errors返回的内容, 这里只做简单示意.

原文始发于微信公众号(Heihu Share):开发基础 | SpringBoot-Vue-Crud 练习项目

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年9月26日13:22:45
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   开发基础 | SpringBoot-Vue-Crud 练习项目http://cn-sec.com/archives/3114706.html

发表评论

匿名网友 填写信息