leyou商城day3 品牌管理

16 篇文章 0 订阅
3 篇文章 0 订阅

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页面中,稍微改动了一点点。

<template>
    <div>
        <v-data-table
                :headers="headers"
                :items="desserts"
                :pagination.sync="pagination"
                :total-items="totalDesserts"
                :loading="loading"
                class="elevation-1"
        >
            <!--
            slot="items" 指定要遍历的数据
            slot-scope="props" 把当前遍历的数据起个别名
            -->
            <template slot="items" slot-scope="props">
                <td>{{ props.item.name }}</td>
                <td class="text-xs-right">{{ props.item.calories }}</td>
                <td class="text-xs-right">{{ props.item.fat }}</td>
                <td class="text-xs-right">{{ props.item.carbs }}</td>
                <td class="text-xs-right">{{ props.item.protein }}</td>
                <td class="text-xs-right">{{ props.item.iron }}</td>
            </template>
        </v-data-table>
    </div>
</template>
<script>
    export default {
        data () {
            return {
                totalDesserts: 0,
                desserts: [],
                loading: true,
                pagination: {},
                headers: [
                    {
                        text: 'Dessert (100g serving)',
                        align: 'left',
                        sortable: false,
                        value: 'name'
                    },
                    { text: 'Calories', value: 'calories' },
                    { text: 'Fat (g)', value: 'fat' },
                    { text: 'Carbs (g)', value: 'carbs' },
                    { text: 'Protein (g)', value: 'protein' },
                    { text: 'Iron (%)', value: 'iron' }
                ]
            }
        },
        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 = true
                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 [
                    {
                        name: 'Frozen Yogurt',
                        calories: 159,
                        fat: 6.0,
                        carbs: 24,
                        protein: 4.0,
                        iron: '1%'
                    },
                    {
                        name: 'Ice cream sandwich',
                        calories: 237,
                        fat: 9.0,
                        carbs: 37,
                        protein: 4.3,
                        iron: '1%'
                    },
                    {
                        name: 'Eclair',
                        calories: 262,
                        fat: 16.0,
                        carbs: 23,
                        protein: 6.0,
                        iron: '7%'
                    },
                    {
                        name: 'Cupcake',
                        calories: 305,
                        fat: 3.7,
                        carbs: 67,
                        protein: 4.3,
                        iron: '8%'
                    },
                    {
                        name: 'Gingerbread',
                        calories: 356,
                        fat: 16.0,
                        carbs: 49,
                        protein: 3.9,
                        iron: '16%'
                    },
                    {
                        name: 'Jelly bean',
                        calories: 375,
                        fat: 0.0,
                        carbs: 94,
                        protein: 0.0,
                        iron: '0%'
                    },
                    {
                        name: 'Lollipop',
                        calories: 392,
                        fat: 0.2,
                        carbs: 98,
                        protein: 0,
                        iron: '2%'
                    },
                    {
                        name: 'Honeycomb',
                        calories: 408,
                        fat: 3.2,
                        carbs: 87,
                        protein: 6.5,
                        iron: '45%'
                    },
                    {
                        name: 'Donut',
                        calories: 452,
                        fat: 25.0,
                        carbs: 51,
                        protein: 4.9,
                        iron: '22%'
                    },
                    {
                        name: 'KitKat',
                        calories: 518,
                        fat: 26.0,
                        carbs: 65,
                        protein: 7,
                        iron: '6%'
                    }
                ]
            }
        }
    }
</script>

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>
        <!--
            headers:定义表格的表头
            items:定义表格的内容
            pagination: 定义表格的分页条内容和排序内容(注意:该对象不需要我们控制,组件自动复制)
            total-items:定义总记录数
            loading: 进度条
        -->
        <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方法,并删除相关连带的部分,最终代码如下:

<template>
    <div>
        <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>
        <v-data-table
                :headers="headers"
                :items="desserts"
                :pagination.sync="pagination"
                :total-items="totalDesserts"
                :loading="loading"
                class="elevation-1"
        >
            <!--
            slot="items" 指定要遍历的数据
            slot-scope="props" 把当前遍历的数据items起个别名
            props中有个固定的属性叫item,是props遍历时的临时变量
            也就是说item就相当于一个品牌对象了
            -->
            <template slot="items" slot-scope="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: 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' }
                ]
            }
        },
        mounted () {

        },
        methods: {

            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: 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: 2036, name: "酷派", image: "4.jpg", letter: "K"},
                    {id: 2036, name: "酷派", image: "4.jpg", letter: "K"},
                    {id: 2036, name: "酷派", image: "4.jpg", letter: "K"},
                    {id: 2037, name: "魅族", image: "5.jpg", letter: "M"}
                ]
            }
        }
    }
</script>

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

在methods中定义方法:

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

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

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

此刻最终代码如下:

<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>
        <!--
            headers:定义表格的表头
            items:定义表格的内容
            pagination: 定义表格的分页条内容和排序内容(注意:该对象不需要我们控制,组件自动复制)
            total-items:定义总记录数
            loading: 进度条
        -->
        <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
                    }
                ]
            }
        },
        mounted () {
            //调用列表查询
            this.loadBranData();
        },
        methods: {
            //后台加载品牌列表数据
            loadBranData(){
                // 异步请求
                this.desserts = this.getDesserts();
                this.totalDesserts = 5;
            },
            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>

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();
            }
        }
    },

最终页面代码为:

<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-model="searchKey"
                ></v-text-field>
            </v-flex>

        </v-layout>
        <!--
            headers: 定义表头信息
            items: 定义表的内容(数据列表) ***
            pagination: 定义分页参数数据  ***
            total-items: 总记录数   ***
            loading: 进度条效果
        -->
        <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: true,
                pagination: {},
                headers: [
                    { text: '品牌编号', value: 'id' , align:'center'},
                    { text: '品牌名称', value: 'name', align:'center' ,sortable:false },
                    { text: '品牌图片', value: 'image', align:'center' ,sortable:false },
                    { text: '品牌字母', value: 'letter', align:'center' },
                ],
                searchKey:'',//搜索关键词
            }
        },
        mounted () {
            //调用分页查询品牌
            this.loadBrandData();
        },
        //监听
        watch:{
            "searchKey":{
                handler(){
                    this.loadBrandData();
                }
            },
            "pagination":{
                deep:true,//深度监听。注意:监听对象里面的属性变化时用到
                handler(){
                    this.loadBrandData();
                }
            }
        },
        methods: {
            //分页查询品牌
            loadBrandData(){
                //请求后台
                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;
                }).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"}
                ]
            }
        }
    }
</script>

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.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

    /**
     * 创建分页拦截器对象
     * @return
     */
    @Bean
    public MybatisPlusInterceptor MybatisPlusInterceptor(){
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页插件
        PaginationInnerInterceptor pageInterceptor = new PaginationInnerInterceptor();
        // 设置请求的页面大于最大页后操作,true调回到首页,false继续请求。默认false
        pageInterceptor.setOverflow(false);
        // 单页分页条数限制,默认无限制
        pageInterceptor.setMaxLimit(500L);
        // 设置数据库类型
        pageInterceptor.setDbType(DbType.MYSQL);

        interceptor.addInnerInterceptor(pageInterceptor);
        return interceptor;
    }
}




在这里插入图片描述

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测试

重启getway

在这里插入图片描述

10、品牌查询:渲染品牌分页查询页面

1)页面接收服务器返回的分页数据

先通过页面f12的控制台查看回调函数中的resp结果的结构是如何的:
在这里插入图片描述

由resp的数据结构得知,回调函数的代码应该为:
在这里插入图片描述

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

3)最终页面代码

<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-model="searchKey"></v-text-field>
        </v-flex>
     </v-layout>
      <!--
           headers: 定义表格的头部信息(*)
           items: 定义表格的内容信息(**)
           pagination: 定义分页相关数据(分页条数据,排序字段数据等)
           total-items:总记录数(**)
           loading: 控制进度条
       -->
    <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"><img :src="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: true,
        pagination: {},
        headers: [
          { text: '品牌编号', align: 'center' , value: 'id' },
          { text: '品牌名称', align: 'center' , sortable: false,  value: 'name' },
          { text: '品牌图片', align: 'center' , sortable: false,  value: 'image' },
          { text: '品牌字母', align: 'center' , value: 'letter' },
        ],
        searchKey:'',//搜索关键词
      }
    },
    mounted () {
      this.loadBrandData();
    },
    watch:{
        //监听searchKey的变化
        "searchKey":{
            handler(){
                this.loadBrandData();
            }
        },
        "pagination":{
            deep:true, //注意:如果vue需要监听的是对象的属性变化,必须使用深度监听
            handler(){
                this.loadBrandData();
            }
        }
    },
    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 = resp.data.items;
               this.totalDesserts = resp.data.total;
               //关闭进度条
               this.loading = false;
           }).catch(e=>{
               console.log('加载品牌数据失败');
           })
       }
    }
  }
</script>

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<Long> 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:

<?xml version="1.0" encoding="UTF-8" ?>
<!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">

    <!--  
       collection: 需要遍历的数据(数组或集合)
       item: 每个的元素的别名
       separator: (可选)分隔符
      -->
   <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 操作两种表,品牌表,品牌分类表

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值