leyou商城day3

leyou商城day3,第1张

01、学习目标 实现品牌查询功能独立实现品牌新增 02、从零开始制作页面:添加品牌菜单

https://v15.vuetifyjs.com/zh-Hans/getting-started/quick-start

商品分类完成以后,自然轮到了品牌功能了。

分析左侧菜单的数据:



先看看我们要实现的效果:

接下来,我们从0开始,实现下从前端到后端的完整开发。

提供自定义品牌菜单项

在menu.js中添加菜单名称

在路由router.js指定菜单path所跳转的页面

在对应跳转路径下新建一个MyBrand.vue空文件

MyBrand.vue的内容:

<template>
    <div>我的品牌页面div>
template>

03、从零开始制作页面:品牌列表展示

参考文档: https://v15.vuetifyjs.com/en/getting-started/quick-start

1)找到一个服务端分页组件

点进去源码

分别复制template和script到MyBrand.vue页面中,稍微改动了一点点。



2)页面数据分析【data】

大家注意:

我们看一个vue页面,一定是从script的data中开始。

整个页面要用到的所有数据,都必须在data中定义。

其中data定义的变量的值的来源又分几类:

收集template页面数据到data中

接收后台响应的数据到data中

纯定义数据在页面使用的

查看data中的数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9T0xQ4gF-1652332569645)(assets/image-20200314094318516.png)]

把表头信息改成我们自己的品牌表的字段

data () {
    return {
        totalDesserts: 0,//总记录数
        desserts: [],//当前页的数据列表
        loading: true,//加载进度条的特效
        pagination: {},//分页插件
        headers: [//表头信息
            { text: '品牌编号', align: 'center', value: 'id'},
            { text: '品牌名称', align: 'center', value: 'name' },
            { text: '品牌图片', align: 'center', sortable: false, value: 'image' },
            { text: '品牌字母', align: 'center', value: 'letter' }
        ]
    }
},

修改后页面的效果为:

3)渲染列表数据【通过钩子函数获取到要渲染的数据】

上面,我们已经在data中定义好页面使用的数据了。

接下来,我们要在钩子函数中,向服务器发起请求,并获取数据列表和分页信息。

首先,模拟服务器返回的数据

 [
     {id: 2032, name: "OPPO", image: "1.jpg", letter: "O"},
     {id: 2033, name: "飞利浦", image: "2.jpg", letter: "F"},
     {id: 2034, name: "华为", image: "3.jpg", letter: "H"},
     {id: 2036, name: "酷派", image: "4.jpg", letter: "K"},
     {id: 2037, name: "魅族", image: "5.jpg", letter: "M"}
 ]

品牌中有id,name,image,letter字段。把这些数据放入页面中。

再次查看页面效果:

在template中找到哪个地方使用了数据列表的变量

我们已经知道凡是":"号开头的属性,都是可以识别vue数据的属性。这里这个:item就是分页列表插件接收列表数据的属性,具体遍历在下面这段代码中:

此刻页面效果为:

4)总结vue页面渲染数据的流程

第一步:在data中定义数据

第二步:在钩子函数中发起请求

第三步:如果请求需要参数,在data中直接拿,不需要参数则忽略步骤

第四步:发起服务器请求,在回调函数中,把服务器返回的数据,赋值给data中定义的变量

第五步:直接在template中渲染数据

04、从零开始制作页面:添加按钮和搜索框

在table上面加入如下代码:

		<v-layout row wrap>
            <v-flex xs6>
                <v-btn round color="primary" dark>品牌添加v-btn>
            v-flex>
            <v-flex xs6>
            <v-text-field
                    label="搜索"
                    prepend-icon="search"
            >v-text-field>
            v-flex>
        v-layout>

此刻效果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dvjlrdv4-1652332569647)(assets/image-20200314103420235.png)]

MyBrand.vue页面

<template>
    <div>
        <v-layout row wrap>
            <v-flex xs6>
                <v-btn color="primary" round>品牌添加v-btn>
            v-flex>

            <v-flex xs6>
                <v-text-field
                        label="搜索"
                        prepend-icon="search"
                >v-text-field>
            v-flex>

        v-layout>
        
        <v-data-table
                :headers="headers"
                :items="desserts"
                :pagination.sync="pagination"
                :total-items="totalDesserts"
                :loading="loading"
                class="elevation-1"
        >
            <template v-slot:items="props">
                <td class="text-xs-center">{{ props.item.id }}td>
                <td class="text-xs-center">{{ props.item.name }}td>
                <td class="text-xs-center">{{ props.item.image }}td>
                <td class="text-xs-center">{{ props.item.letter }}td>
            template>
        v-data-table>
    div>
template>

<script>
    export default {
        data () {
            return {
                totalDesserts: 0,
                desserts: [],
                loading: false,
                pagination: {},
                headers: [
                    {
                        text: '品牌编号',
                        align: 'center',
                        sortable: true
                    },
                    {
                        text: '品牌名称',
                        align: 'center',
                        sortable: false
                    },
                    {
                        text: '品牌图片',
                        align: 'center',
                        sortable: false
                    },
                    {
                        text: '品牌首字母',
                        align: 'center',
                        sortable: true
                    }
                ]
            }
        },
        watch: {
            pagination: {
                handler () {
                    this.getDataFromApi()
                        .then(data => {
                            this.desserts = data.items
                            this.totalDesserts = data.total
                        })
                },
                deep: true
            }
        },
        mounted () {
            this.getDataFromApi()
                .then(data => {
                    this.desserts = data.items
                    this.totalDesserts = data.total
                })
        },
        methods: {
            getDataFromApi () {
                this.loading = false
                return new Promise((resolve, reject) => {
                    const { sortBy, descending, page, rowsPerPage } = this.pagination

                    let items = this.getDesserts()
                    const total = items.length

                    if (this.pagination.sortBy) {
                        items = items.sort((a, b) => {
                            const sortA = a[sortBy]
                            const sortB = b[sortBy]

                            if (descending) {
                                if (sortA < sortB) return 1
                                if (sortA > sortB) return -1
                                return 0
                            } else {
                                if (sortA < sortB) return -1
                                if (sortA > sortB) return 1
                                return 0
                            }
                        })
                    }

                    if (rowsPerPage > 0) {
                        items = items.slice((page - 1) * rowsPerPage, page * rowsPerPage)
                    }

                    setTimeout(() => {
                        this.loading = false
                        resolve({
                            items,
                            total
                        })
                    }, 1000)
                })
            },
            getDesserts () {
                return [
                    {id: 2032, name: "OPPO", image: "1.jpg", letter: "O"},
                    {id: 2033, name: "飞利浦", image: "2.jpg", letter: "F"},
                    {id: 2034, name: "华为", image: "3.jpg", letter: "H"},
                    {id: 2036, name: "酷派", image: "4.jpg", letter: "K"},
                    {id: 2037, name: "魅族", image: "5.jpg", letter: "M"}
                ]
            }
        }
    }
script>
05、从零开始制作页面:改为服务端分页 1)删除客户端分页的代码

虽然我们用的是服务端分页的插件,但是vuetify为了便于前端工作人员,可以更好的调整页面样式,给我们临时做了一个页面效果出来,我们要去掉这个效果,其实就是删除getDataFromApi方法,并删除相关连带的部分,最终代码如下:



2)自己来定义一个服务端分页的请求方法

在methods中定义方法:

loadBrandData(){
    this.desserts = this.getDesserts ()//给当前data中的数据列表赋值
    this.totalDesserts = 20 //给总数据量赋值
},

在钩子函数中调用上面的方法:

mounted () {
    //发起服务端分页请求
    this.loadBrandData()
},

此刻最终代码如下:




06、从零开始制作页面:向后端请求品牌分页数据 说明

截至目前,我们的品牌静态页面已经做好了,接下来,就是vue代码部分了!

使用vue把静态页面变成动态页面的步骤如下:

第一步:参考开发api文档

第二步:根据api要求提供要向后台传输的参数

在data中定义一个变量接收搜索数据

在页面上使用双向绑定给searchKey赋值

如果我们使用了vuetify,那么所有分页相关的数据,无需自己在data中定义,在data中的pagination是空对象,但是我们用vue的chrome插件查看页面的数据发现,里面都已经包含了所有的分页条件,如下:

条件如下:

{"descending":false,"page":1,"rowsPerPage":5,"sortBy":"id","totalItems":0}

我们在代码中并没有给pagination赋值,但是发现打开页面它就自己有值了,所以是框架给我们赋的值,也就是说这些数据我们都不用关心了,我们只需要把后台的数据正确返回页面就能显示了。

第三步:页面发起后台服务器请求

修改向后台发起请求的方法:

methods: {
           //加载后台品牌数据
       loadBrandData(){
          //ajax异步请求后台
           this.$http.get('/item/brand/page',{
               params:{
                   page:this.pagination.page,
                   rows:this.pagination.rowsPerPage,
                   key:this.searchKey,
                   sortBy:this.pagination.sortBy,
                   desc:this.pagination.descending
               }
           }).then(resp=>{
               this.desserts = this.getDesserts();
               this.totalDesserts = 20;
               //关闭进度条
               this.loading = false;
           }).catch(e=>{
               console.log('加载品牌数据失败');
           })
       },

            getDesserts () {
                return  [
                    {id: 2032, name: "OPPO", image: "1.jpg", letter: "O"},
                    {id: 2033, name: "飞利浦", image: "2.jpg", letter: "F"},
                    {id: 2034, name: "华为", image: "3.jpg", letter: "H"},
                    {id: 2036, name: "酷派", image: "4.jpg", letter: "K"},
                    {id: 2037, name: "魅族", image: "5.jpg", letter: "M"}
                ]
            }
        }

通过页面f12在网络中查看效果

07、从零开始制作页面:监听分页参数与查询条件

添加watch的监听事件,由于分页参数是一个对象,所以我们需要使用深度监听。

在Vue中添加watch事件

watch:{
        //监听searchKey的变化
        "searchKey":{
            handler(){
                this.loadBrandData();
            }
        },
        "pagination":{
            deep:true, //注意:如果vue需要监听的是对象的属性变化,必须使用深度监听
            handler(){
                this.loadBrandData();
            }
        }
    },

最终页面代码为:




08、编写品牌后台接口:品牌模块的准备工作 1)通用的分页对象

我们把这个对象放到 ly-common 模块的pojo包中。

package com.leyou.common.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

/**
 * 通用的分页封装对象
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageResult<T> {
    private Long total;//总记录数
    private Long totalPage;//总页数
    private List<T> items;
}


2)tb_brand表的基本类

用逆向工程生成代码,并将实体类放到ly-pojo-item下

这边发现上次导入之后无法生成代码,原因是因为2个版本的mybatis版本核心包不一致。

将他改成3.4.1版本即可



## 09、编写品牌后台接口:品牌分页查询(**)

### 1) 独立创建MybatisPlus分页拦截器(*)

编写分页配置类

```java
package com.leyou.item.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * 分页插件配置类
 */
@Configuration
public class PageHelperConfiguration {

    /**
     * 创建分页插件拦截器对象
     */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        return new PaginationInterceptor();
    }

}



2)编写处理器
package com.leyou.item.controller;

import com.leyou.common.pojo.PageResult;
import com.leyou.item.pojo.Brand;
import com.leyou.item.service.BrandService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class BrandController {
    @Autowired
    private BrandService brandService;

    /**
     * 分页查询品牌
     */
    @GetMapping("/brand/page")
    public ResponseEntity<PageResult<Brand>> brandPageQuery(
            @RequestParam(value = "page",defaultValue = "1") Integer page,
            @RequestParam(value = "rows",defaultValue = "5") Integer rows,
            @RequestParam(value = "key",required = false) String key,
            @RequestParam(value = "sortBy",required = false) String sortBy,
            @RequestParam(value = "desc",required = false) Boolean desc
    ){
        PageResult<Brand> pageResult = brandService.brandPageQuery(page,rows,key,sortBy,desc);
        return ResponseEntity.ok(pageResult);
    }
}



3)创建Service
package com.leyou.item.service;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.leyou.common.pojo.PageResult;
import com.leyou.item.mapper.BrandMapper;
import com.leyou.item.pojo.Brand;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@Transactional
public class BrandService {
    @Autowired
    private BrandMapper brandMapper;

    public PageResult<Brand> brandPageQuery(Integer page, Integer rows, String key, String sortBy, Boolean desc) {
        //1.封装条件
        //1.1 IPage: 这里用于封装分页前的参数
        IPage<Brand> iPage = new Page<>(page,rows);

        //1.2 QueryWrapper: 用于封装查询条件(也可以排序)
        QueryWrapper<Brand> queryWrapper = Wrappers.query(); //待会自己往QueryWrapper存入条件

        //处理key
        if(StringUtils.isNotEmpty(key)){
            //sql: where name like '%H%' or letter = 'H'
            /**
             * 参数一:字段名称
             * 注意:like方法的value已经包含%xxx%
             */
            queryWrapper
                    .like("name",key)
                    .or()
                    .eq("letter",key.toUpperCase());
        }

        //处理排序
        if (StringUtils.isNotEmpty(sortBy)) {
            if(desc){
                //降序
                queryWrapper.orderByDesc(sortBy);
            }else{
                //升序
                queryWrapper.orderByAsc(sortBy);
            }
        }


        //2.执行查询,获取结果
        //IPage: 这里的IPage封装分页后的结果
        iPage = brandMapper.selectPage(iPage,queryWrapper);

        //3.处理并返回结果
        //3.1 封装PageResult
        PageResult<Brand> pageResult = new PageResult<>(iPage.getTotal(),iPage.getPages(),iPage.getRecords());
        //3.2 返回
        return pageResult;
    }
}


4)重启ly-item测试

10、品牌查询:渲染品牌分页查询页面 1)页面接收服务器返回的分页数据

先通过页面f12的控制台查看回调函数中的resp结果的结构是如何的:

由resp的数据结构得知,回调函数的代码应该为:

2)修改品牌列表中图片的显示

3)最终页面代码



11、新增品牌:思路分析

品牌查询已经做好了,那么新增品牌我们直接在Brand.vue文件中开发即可。

品牌数据如何添加? 除了自己的数据,还有无其他数据?

之前分析过品牌表和分类表是多对多,分类的数据,一般变动的比较少,所以中间表我们一般让变动比较多的品牌表来维护,也就是说我们添加完品牌数据,还需要在中间表添加数据。

外键:
	逻辑外键:通过代码维护关系,
		优点:便于迁移
		缺点:出现脏数据
	物理外键:数据库维护关系,
		优点:数据完整
		缺点:不方便删除
		

品牌添加页面分析图:

由品牌添加页面分析得知,品牌添加步骤分三步:

第一:先把图片存入到图片服务器

第二:把品牌对象入库

第三:维护品牌和分类的中间表

12、新增品牌:完成品牌的保存和中间表的维护 1)编写Controller
   /**
     * 新增品牌
     * 1) 使用对象接收的情况
     *  Brand brand: 用于接收页面的普通参数,例如:name=xxx&letter=C&image=xxx
     *  @RequestBody Brand brand: 用于接收页面的json参数,例如:{name:"xxx",letter:"C",image:"xxx"}
     *  
     * 2)接收页面的同名参数
     *    页面上有两种情况传递的同名参数
     *     1)复选框提交的格式 例如   ids=1&ids=2&ids=3....
     *     2)使用逗号拼接格式 例如   ids=1,2,3....
     *    后台如何接收同名参数
     *       1)字符串     String ids    内容:1,2,3....
     *       2) 数组    Long[] ids     内容:[1,2,3]
     *       3)List集合  List ids  内容:[1,2,3]  注意:List集合必须添加@RequestParam注解
     */
    @PostMapping("/brand")
    public ResponseEntity<Void> saveBrand(
            Brand brand,
            @RequestParam("cids") List<Long> cids
    ){
        brandService.saveBrand(brand,cids);
        //return ResponseEntity.status(HttpStatus.CREATED).body(null);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    }

2)编写Service

这里要注意,我们不仅要新增品牌,还要维护品牌和商品分类的中间表。

 public void saveBrand(Brand brand, List<Long> cids) {
        try {
            //1.保存品牌表数据
            brandMapper.insert(brand); // insert()方法自动把数据库自增id值赋给Brand的id

            //2.保存分类品牌中间表数据
            brandMapper.saveCategoryAndBrand(brand.getId(),cids);
        } catch (Exception e) {
            e.printStackTrace();
            throw new LyException(ExceptionEnum.INSERT_OPERATION_FAIL);
        }

    }
3)编写Mapper
package com.leyou.item.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.leyou.item.pojo.Brand;
import org.apache.ibatis.annotations.Param;

import java.util.List;

public interface BrandMapper extends BaseMapper<Brand> {
    
    void saveCategoryAndBrand(@Param("bid") Long id,@Param("cids") List<Long> cids);
}


在resource下新建一个目录:mappers,并在下面新建文件:BrandMapper.xml

然后在application.yml文件中配置mapper文件的地址:

mybatis-plus:
  type-aliases-package: com.leyou.item.pojo
  configuration:
    map-underscore-to-camel-case: true
  mapper-locations: classpath:mappers/*.xml #修改Mapper映射文件的路径

BrandMapper.xml中定义Sql的statement:


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.leyou.item.mapper.BrandMapper">

    
   <insert id="saveCategoryAndBrand">
        INSERT INTO tb_category_brand(category_id,brand_id) VALUES 
        <foreach collection="cids" item="cid" separator=",">
            (#{cid},#{bid})
        foreach>
   insert>
mapper>

编写完成代码后,重启微服务,测试查看品牌和中间表的数据是否已经插入即可!

4)开启myBatis日志

# 开启日志  
logging:
  level:
    com.leyou: debug
13、总结

1)品牌分页列表

​ 1.1 了解页面使用Vuetify组件完成基本页面制作

​ 1.2 熟悉前端ajax请求后台过程!!!!(请求方式,路径,参数,返回值)

​ 1.3 完成品牌列表展示(MyBatis-Plus 带条件分页)

2)品牌添加

​ 2.1 *** 作两种表,品牌表,品牌分类表

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/940586.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-17
下一篇 2022-05-17

发表评论

登录后才能评论

评论列表(0条)

保存