功能实现02

后端:https://github.com/liyuelian/furniture-back-end.git

前端:https://github.com/liyuelian/furniture-front-end.git

3.功能03-添加家居信息

3.1需求分析

浏览器页面点击添加按钮,弹出提示框,填写家居信息,点击确定,可以把数据添加到数据库中。

3.2思路分析

  1. 完成后台代码 dao->service->controller,并对每层代码进行测试,到controller层使用postman发送http post请求完成测试
  2. 完成前端代码,使用axios 发送ajax请求(json数据)给后台,实现添加家居信息

3.3代码实现

image-20230306205850299

后端项目使用的是 ssm 框架,src 下分为 bean,dao,service,controller,utils层,其中 controller 层由springmvc 接管,service 层由 spring 接管,dao 层由 mybatis 接管。

相比于传统的 Javaweb 项目,它的三层结构并没有发生根本变化,仅仅是框架化了,各组件由框架接管。原本的 jsp 则页面变成了 vue 前端项目。

3.3.1后端代码

由于使用了逆向工程,bean 层、dao 层以及dao层接口对应的mapper映射文件已经生成,因此暂时不必在这两层编写代码了。

(1)Service层,创建对应的接口和实现类

FurnService.java(接口)

package com.li.furn.service;

import com.li.furn.bean.Furn;

/**
 * @author 李
 * @version 1.0
 */
public interface FurnService {
    //添加
    public void save(Furn furn);
}

FurnServiceImpl.java(实现类)

package com.li.furn.service.impl;

import com.li.furn.bean.Furn;
import com.li.furn.dao.FurnMapper;
import com.li.furn.service.FurnService;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @author 李
 * @version 1.0
 */
@Service //注入spring容器
public class FurnServiceImpl implements FurnService {
    //自动装配 FurnMapper接口对象(代理对象)
    @Resource
    private FurnMapper furnMapper;

    //已经在spring配置文件中开启事务
    @Override
    public void save(Furn furn) {
        furnMapper.insertSelective(furn);
    }
}

测试代码:

package com.li.furn.test.service;

import com.li.furn.bean.Furn;
import com.li.furn.service.FurnService;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.math.BigDecimal;

/**
 * @author 李
 * @version 1.0
 */
public class FurnServiceTest {
    //spring容器
    private ApplicationContext ioc;
    //从spring容器中获取的是FurnService接口的代理对象
    private FurnService furnService;

    @Before
    public void init() {
        ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        //通过FurnService.class类型获取FurnService接口对象的代理对象
        furnService = ioc.getBean(FurnService.class);
    }

    @Test
    public void FurnServiceTest() {
        //添加数据
        Furn furn = new Furn(null, "复古沙发", "森林之家",
                new BigDecimal(1088), 12, 28,
                "/assets/images/product-image/7.jpg");
        furnService.save(furn);
        System.out.println("添加成功");
        //关闭sqlSession的动作底层会自动释放
    }
}

测试成功:

day03-功能实现02-小白菜博客
image-20230306213121934

(2)关于家居图片路径

给新增的家居增加一个默认图片的路径值,修改Furn.java(部分代码):

//默认图片路径
private String imgPath = "assets/images/product-image/1.jpg";

public Furn(Integer id, String name, String maker, BigDecimal price,
            Integer sales, Integer stock, String imgPath) {
    this.id = id;
    this.name = name;
    this.maker = maker;
    this.price = price;
    this.sales = sales;
    this.stock = stock;
    //如果新的家具信息的图片不为空,或者不为空串时,就设置,否则为默认值
    //imgPath != null && !imgPath.equals("")
    // =>使用StringUtils.hasText()代替,该方法要求传入的字符串不是null,并且不是"",并且不能全为空格
    if (StringUtils.hasText(imgPath)) {
        this.imgPath = imgPath;
    }
}

(3)bean层创建Msg.java,用来返回给前端 Json 数据的通用类,本质就是一个”协议“

image-20230306220803505

package com.li.furn.bean;

import java.util.HashMap;
import java.util.Map;

/**
 * @author 李
 * @version 1.0
 * 用来返回给前端Json数据的通用类
 */
public class Msg {
    //状态码 200-成功 , 400-失败
    private int code;
    //信息-对返回的数据的说明
    private String msg;
    //返回给浏览器的数据-Map集合
    private Map<String, Object> extend = new HashMap<>();

    //几个常用的方法-封装好msg

    //返回一个success的 msg说明
    public static Msg success() {
        Msg msg = new Msg();
        msg.setCode(200);
        msg.setMsg("success");
        return msg;
    }

    //返回一个fail的 msg说明
    public static Msg fail() {
        Msg msg = new Msg();
        msg.setCode(400);
        msg.setMsg("fail");
        return msg;
    }

    //给返回的msg设置数据
    public Msg add(String key, Object value) {
        extend.put(key, value);
        return this;//返回的是当前Msg对象
    }

    //省略setter,getter方法
}

(4)创建FurnController.java,处理添加家居请求

package com.li.furn.controller;

import com.li.furn.bean.Furn;
import com.li.furn.bean.Msg;
import com.li.furn.service.FurnService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.annotation.Resource;

/**
 * @author 李
 * @version 1.0
 */
@Controller//由springmvc来处理
public class FurnController {
    @Resource
    private FurnService furnService;

    /**
     * 1.响应客户端添加家居的请求
     * 2.@RequestBody 注解将客户端提交的json数据封装成 Javabean 对象。
     * 3.@ResponseBody 服务器返回的数据是按照json来返回的(底层是按照 http协议进行协商)
     *
     * @param furn
     * @return
     */
    @ResponseBody
    @PostMapping("/save")
    public Msg save(@RequestBody Furn furn) {
        furnService.save(furn);
        //如果没有出现异常,就返回成功
        Msg success = Msg.success();
        return success;
    }
}

(5)Postman对Controller层进行测试

使用postman测试时,因为我们前台发送的是json数据,被服务器接收到后,转成Javabean数据,因此pom.xml需要引入jackson,处理json数据,否则后台会报错。

<!--引入jackson-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.4</version>
</dependency>

image-20230307204911642

请求结果:

image-20230307204815755image-20230307205031536

3.3.2前端代码

(1)修改HomeView.vue页面,代码略。

<template>
  <div>
    <!--增加按钮和搜索框-->
    <div style="margin:10px 5px">
      <el-button type="primary" @click="add">新增</el-button>
      <el-button>其他</el-button>
    </div>
    <div style="margin:10px 5px">
      <el-input v-model="search" style="width: 30%" placeholder="请输入关键字"/>
      <el-button style="margin-left: 10px" type="primary">查找</el-button>
    </div>

    <!--表格-->
      ...

    <!--添加家居的弹窗
      1.el-dialog v-model="dialogVisible" 表示对话框,
            和 dialogVisible 变量双向绑定,控制是否显示对话框
      2.el-form:mode="form" 表示表单数据和form数据变量双向绑定
      3.el-input v-mode="form.name" 表示表单的input控件,
            名字为name,必须需要和后端Javabean属性一致
      4.在前端中,对象的属性是可以动态生成的-->
    <el-dialog title="提示" v-model="dialogVisible" width="40%">
      <el-form :model="form" label-width="120px">
        <el-form-item label="家居名">
          <el-input v-model="form.name" style="width: 80%"></el-input>
            ...
            ...
      </el-form>
      <template #footer>
        <span class="dialog-footer">
          <el-button @click="dialogVisible=false">取 消</el-button>
          <el-button type="primary" @click="save">确 定</el-button>
        </span>
      </template>
    </el-dialog>
  </div>
</template>

<script>
import request from '@/utils/request.js'

//导出组件
export default {
  name: 'HomeView',
  components: {},
  data() {
    return {
      search: '',
      dialogVisible: false,
      form: {},//定义一个空表单
      tableData: [...]
    }
  },
  methods: {
    add() {//显示添加对话框
      //显示对话框
      this.dialogVisible = true;
      //每次点击都要清空上一次的表单数据
      this.form = {};
    },
    save() {//将填写的表单数据发送给后端
      //第一个参数为url,第二个参数是请求携带的数据
      request.post("/api/save", this.form).then(res => {
        console.log("res-", res)
        this.dialogVisible = false;//隐藏表单
      })
    }
  }
}
</script>

效果如下:

image-20230308193159713

(2)安装axios,用于发送ajax请求给后台

image-20230308193549442

(3)在前端项目的src目录下创建utlis目录,utils目录下创建工具文件request.js,用于创建axios request对象,发送ajax请求。

//引入axios包
import axios from "axios";
//通过axios创建request对象,用于发送请求到后端
const request = axios.create({
    timeout: 5000
})

//request拦截器的处理,它可以对请求做统一的处理
//1.可以对请求做统一的处理
//2.比如统一地加入token,Content-Type
request.interceptors.request.use(config => {
    config.headers['Content-Type'] = 'application/json;charset=utf-8';
    return config;
}, error => {
    return Promise.reject(error)
})

//导出request对象,在其他文件引入即可使用
export default request;

如果启动前端项目,提示找不到axios,需要把光标放在import axios from ‘axios’ 的提示上,会有一个修复提示,点击导入axios即可。

(4)修改HomeView.vue,在methods编写save方法,并测试。

...
save() {//将填写的表单数据发送给后端
  //第一个参数为url,第二个参数是请求携带的数据
  request.post('http://localhost:8080/save', this.form).then(res => {
    console.log("res-", res)
    this.dialogVisible = false;//隐藏表单
  })
}
...

测试向后端项目发送请求,因为前后端的服务ip不一致,会出现跨域问题,浏览器会阻止访问:

image-20230308200155412

(5)在项目目录下的vue.config.js,解决跨域问题

...
module.exports = {
    devServer: {
        port: 10001,//启动端口
        proxy: {//设置代理,必须填
            '/api': {//设置拦截器  拦截器格式
                target: 'http://localhost:8080/',//代理的目标地址,就是/api代替的地址
                changeOrigin: true,//是否设置同源,如果为true,就允许跨域
                pathRewrite: {//路径重写
                    '/api': ''//选择忽略拦截器里面的单词
                }
            }
        }
    }
}

因为修改了配置文件,npm serve 需要重启,否则不能识别。

再次测试,成功发送数据:

image-20230308203141229

3.4注意事项和细节

  1. 一定要确定request.post("/api/save")被代理后的url是项目后台服务对应提供的api接口url,否则报404
  2. postman测试时,要指定content-type,否则会出错(415错误)
  3. 如果后端需要将json提交的数据封装到对应的Javabean中,需要配置@RequestBody,否则会报错500
  4. 如果后端需要返回json数据,需要在方法上配置@ResponseBody,否则会报错404

4.功能04-显示家居信息

4.1需求分析

在页面中展示数据库的家居信息。

4.2思路分析

  1. 完成后台代码从dao-service-controller,并对每层代码进行测试
  2. 完成前台代码,使用axios发送http请求,返回所有家居数据,将数据绑定展示

4.3代码实现

4.3.1后端代码

分层完成,由于使用了逆向工程,bean 层、dao 层以及dao层接口对应的mapper映射文件已经生成,因此暂时不必在这两层编写代码了。

(1)service层,修改FurnService.java和FurnServiceImpl.java,增加findAll()方法。(展示不考虑分页问题)

FurnService.java:

//查询所有的家居信息
public List<Furn> findAll();

FurnServiceImpl.java:

@Override
public List<Furn> findAll() {
    //如果传入为null表示返回所有的家居信息
    return furnMapper.selectByExample(null);
}

(2)controller层,FurnController.java 处理现实家居的请求

@RequestMapping("/furns")
@ResponseBody
public Msg listFurns() {
    List<Furn> furnList = furnService.findAll();
    //将数据封装到 Meg对象中返回
    Msg msg = Msg.success();
    msg.add("furnList", furnList);
    return msg;
}

(3)postman测试成功

day03-功能实现02-小白菜博客
image-20230308210703467

4.3.2前端代码

(1)修改src/utils/request.js,增加response拦截器,统一处理响应后的结果

//response拦截器,可以在调用接口响应后,统一的处理返回结果
request.interceptors.response.use(
    response => {
        let res = response.data;
        //如果返回的是文件
        if (response.config.responseType === 'blob') {
            return res;
        }
        //如果返回的是string,就转成json对象
        if (typeof res === 'string') {
            //如果不为空,就转成json对象
            res = res ? JSON.parse(res) : res;
        }
        return res;
    }, error => {
        //如果失败
        console.log("err=", error);
        return Promise.reject(error);
    })

(2)修改HomeView.vue,当现实页面之前就发出请求,然后将接收的数据显示到页面上

<template>
  <div>
      ...

    <!--表格显示家居新-->
    <el-table :data="tableData" stripe style="width: 90%">
      <el-table-column prop="id" label="ID" sortable></el-table-column>
      <el-table-column prop="name" label="家居名"></el-table-column>
        ...
        ...
      <el-table-column fixed="right" label="操作" width="100">
        <template #default="scope">
          <el-button link type="primary" size="small"
                     @click="handleEdit(scope.row)">编辑
          </el-button>
          <el-button link type="primary" size="small">删除</el-button>
        </template>
      </el-table-column>
    </el-table>

	...

  </div>
</template>

<script>

import request from '@/utils/request.js'

//导出组件
export default {
  name: 'HomeView',
  components: {},
  data() {
    return {
      search: '',
      dialogVisible: false,
      form: {},//定义一个空表单
      tableData: []
    }
  },
  created() {//生命周期函数
    this.list();
  },
  methods: {
    add() {//显示添加对话框
    ...
    },
    save() {//将填写的表单数据发送给后端
      request.post("/api/save", this.form).then(res => {
        console.log("res-", res)
        this.dialogVisible = false;
        //调用list方法,刷新页面显示的数据
        this.list();
      })
    },
    list() {//list方法,请求返回家居信息,当我们打开页面的时候,该方法就应该自动触发
      request.get("/api/furns").then(res => {//res已经经过拦截器的处理>res=res.data
        //根据res的结构来获取数据
        this.tableData = res.extend.furnList;
      })
    }
  }
}
</script>

image-20230308214201082