Briefly analyze the structure of the commodity classification table
First, let’s talk about the relationship between the classification table and the brand table
Let’s talk about the relationship between the classification table and the brand table and the commodity table
Now we have to create sql statements at the beginning, here we analyze the fields
The database used is the table heima->tb_category
Now go to the database and create this table
Next, before we go to write an entity class, let's take a look at the request method, request path, request parameters, and return data of this class.
Next, write the entity class
Entities are placed in
There was a small episode. At the beginning, some modules on the right side of my maven project were grayed out. After I imported dependencies, all annotations could not be used. The solution is as follows
Then import the dependencies again
Let's first complete an entity class of our commodity classification table
Category.java
package com.leyou.item.pojo;
import lombok.Data;
import tk.mybatis.mapper.annotation.KeySql;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* Created by Administrator on 2023/8/28.
*/
@Table(name="tb_category")
@Data
public class Category {
@Id
@KeySql(useGeneratedKeys = true)
private Long id;
private String name;
private Long parentId;
private Boolean isParent;
private Integer sort;
}
Then go to ly-item-service to write specific business logic, such as mapper, service, and web are all in it
Here is a dependency problem
Introduced the dependency of spring-boot-starter-web, which also includes the core dependency of spring
Let me tell you what our path is when writing this controller class. The path is the url passed by us when we access each interface.
What is the ResponseEntity class for?
There are two format usages
CollectionUtils tool class
This is a tool class provided by Spring
We can do the following tests
Let's paste the code of this CategoryController
package com.leyou.item.web;
import com.leyou.item.pojo.Category;
import com.leyou.item.service.CategoryService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Created by Administrator on 2023/8/29.
*/
@RestController
@RequestMapping("/category")
public class CategoryController {
@Autowired
private CategoryService categoryService;
/**
* 根据父节点的id查询商品分类
* @param pid
* @return
*/
@GetMapping("/list")
public ResponseEntity<List<Category>> queryCategoryListByPid(@RequestParam("pid")Long pid) {
try {
if(pid == null || pid.longValue() < 0) {
//会返回带着状态码的对象400 参数不合法
// return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
//可以做一格优化,下面的类似
return ResponseEntity.badRequest().build();
}
//开始利用service执行查询操作
List<Category> categoryList = categoryService.queryCategoryListByParentId(pid);
if(CollectionUtils.isEmpty(categoryList)) {
//如果结果集为空,响应404
return ResponseEntity.notFound().build();
}
//查询成功,响应200
return ResponseEntity.ok(categoryList);//这里才真正放了数据
} catch (Exception e) {
e.printStackTrace();
}
//自定义状态码,然后返回
//500返回一个服务器内部的错误
//这里也可以不返回,程序出错,本身就会返回500
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build()
}
}
Next, we go to Service to create queryCategoryListByParentId method
Take a look at the full code
package com.leyou.item.service;
import com.leyou.item.mapper.CategoryMapper;
import com.leyou.item.pojo.Category;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* Created by Administrator on 2023/8/29.
*/
@Service
public class CategoryService {
@Autowired
private CategoryMapper categoryMapper;
/**
* 根据父节点的id来查询子结点
* @param pid
* @return
*/
public List<Category> queryCategoryListByParentId(Long pid) {
Category category = new Category();
category.setParentId(pid);
return categoryMapper.select(category);
}
}
The above is done, now go to the database to operate and insert the data in the classification, similar to the following data
The following is the data that exists in the data
Below I start to start:
Our data must go through the gateway
The gateway obviously we can see the data
But when you click in the project, you can’t get out
The above is obviously a cross-domain problem
Cross-domain we just do a configuration on the server side
To put it simply, the server will configure the following information for us
We can configure this information with a class on the server here.
Here we use a cors cross-domain filter written by SpringMVC for us: CrosFilter
The specific code is as follows
package com.leyou.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
/**
* Created by Administrator on 2023/8/31.
*/
@Configuration
public class LeyouCorsConfiguration {
@Bean
public CorsFilter corsFilter() {
//1.添加CORS配置信息
CorsConfiguration config = new CorsConfiguration();
//1) 允许的域,不要写*,否则cookie就无法使用了
config.addAllowedOrigin("http://manage.leyou.com");
config.addAllowedOrigin("http://www.leyou.com");
//2) 是否发送Cookie信息
config.setAllowCredentials(true);
//3) 允许的请求方式
config.addAllowedMethod("OPTIONS");
config.addAllowedMethod("HEAD");
config.addAllowedMethod("GET");
config.addAllowedMethod("PUT");
config.addAllowedMethod("POST");
config.addAllowedMethod("DELETE");
config.addAllowedMethod("PATCH");
// 4)允许的头信息
config.addAllowedHeader("*");
//2.添加映射路径,我们拦截一切请求
UrlBasedCorsConfigurationSource configSource = new UrlBasedCorsConfigurationSource();
configSource.registerCorsConfiguration("/**", config);
//3.返回新的CorsFilter.
return new CorsFilter(configSource);
}
}
Restart the gateway server
Let's talk about brand query
What we have to do now is to find out the above brands
We must figure out the request method, request path, request parameters, and response data to determine the return value
Generally speaking, if the page wants to display a list, it must return a List collection object or return a pagination object
We must define a pagination object
Everyone needs to use the paging object later, so we put it in the common
Let’s make this paging object first
package com.leyou.common.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* Created by Administrator on 2023/9/2.
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageResult<T> {
private Long total;//总条数
private Integer totalPage;//总页数
private List<T> items;//当前页面数据对象
public PageResult(Long total,List<T> items) {
this.total = total;
this.items = items;
}
}
Let's do the front-end page
Go to this page first, and check where the path of the product is in menu.js
The above is the path of the brand /item/brand, let’s see where the components are
go to this location below
Go to our routing page at this location
All page components are placed in pages
Let's write the components ourselves here
We define a MyBrand1.vue component ourselves
Our page is mainly to make a paging table
You can find it in Vuetify
Here we should find the data that has been paged and sorted from the server
Below we can see the template code inside
The above is the template code we want to use
The data script is of course you are in the script, you can check it out
<script>
const desserts = [
{
name: 'Frozen Yogurt',
calories: 159,
fat: 6.0,
carbs: 24,
protein: 4.0,
iron: '1',
},
{
name: 'Jelly bean',
calories: 375,
fat: 0.0,
carbs: 94,
protein: 0.0,
iron: '0',
},
{
name: 'KitKat',
calories: 518,
fat: 26.0,
carbs: 65,
protein: 7,
iron: '6',
},
{
name: 'Eclair',
calories: 262,
fat: 16.0,
carbs: 23,
protein: 6.0,
iron: '7',
},
{
name: 'Gingerbread',
calories: 356,
fat: 16.0,
carbs: 49,
protein: 3.9,
iron: '16',
},
{
name: 'Ice cream sandwich',
calories: 237,
fat: 9.0,
carbs: 37,
protein: 4.3,
iron: '1',
},
{
name: 'Lollipop',
calories: 392,
fat: 0.2,
carbs: 98,
protein: 0,
iron: '2',
},
{
name: 'Cupcake',
calories: 305,
fat: 3.7,
carbs: 67,
protein: 4.3,
iron: '8',
},
{
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',
},
]
const FakeAPI = {
async fetch ({ page, itemsPerPage, sortBy }) {
return new Promise(resolve => {
setTimeout(() => {
const start = (page - 1) * itemsPerPage
const end = start + itemsPerPage
const items = desserts.slice()
if (sortBy.length) {
const sortKey = sortBy[0].key
const sortOrder = sortBy[0].order
items.sort((a, b) => {
const aValue = a[sortKey]
const bValue = b[sortKey]
return sortOrder === 'desc' ? bValue - aValue : aValue - bValue
})
}
const paginated = items.slice(start, end)
resolve({ items: paginated, total: items.length })
}, 500)
})
},
}
export default {
data: () => ({
itemsPerPage: 5,
headers: [
{
title: 'Dessert (100g serving)',
align: 'start',
sortable: false,
key: 'name',
},
{ title: 'Calories', key: 'calories', align: 'end' },
{ title: 'Fat (g)', key: 'fat', align: 'end' },
{ title: 'Carbs (g)', key: 'carbs', align: 'end' },
{ title: 'Protein (g)', key: 'protein', align: 'end' },
{ title: 'Iron (%)', key: 'iron', align: 'end' },
],
serverItems: [],
loading: true,
totalItems: 0,
}),
methods: {
loadItems ({ page, itemsPerPage, sortBy }) {
this.loading = true
FakeAPI.fetch({ page, itemsPerPage, sortBy }).then(({ items, total }) => {
this.serverItems = items
this.totalItems = total
this.loading = false
})
},
},
}
</script>
Next, let's take a look at what the brand table shows. Let's take a look at the fields in the database. First, create a product table, and then insert the data into it
Next, we insert the data, similar to inserting the following data
Take a look, it is obvious that the data in this table already exists
We can directly modify the header from the following position
The following directly shows all the codes on the front end of the brand page
<template>
<v-card>
<v-card-title>
<v-btn color="primary" @click="addBrand">新增品牌</v-btn>
<!--搜索框,与search属性关联-->
<v-spacer/>
<v-flex xs3>
<v-text-field label="输入关键字搜索" v-model.lazy="search" append-icon="search" hide-details/>
</v-flex>
</v-card-title>
<v-divider/>
<v-data-table
:headers="headers"
:items="brands"
:pagination.sync="pagination"
:total-items="totalBrands"
:loading="loading"
class="elevation-1"
>
<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">
<img v-if="props.item.image" :src="props.item.image" width="130" height="40">
<span v-else>无</span>
</td>
<td class="text-xs-center">{
{ props.item.letter }}</td>
<td class="justify-center layout px-0">
<v-btn flat icon @click="editBrand(props.item)" color="info">
<i class="el-icon-edit"/>
</v-btn>
<v-btn flat icon @click="deleteBrand(props.item)" color="purple">
<i class="el-icon-delete"/>
</v-btn>
</td>
</template>
</v-data-table>
<!--弹出的对话框-->
<v-dialog max-width="500" v-model="show" persistent scrollable>
<v-card>
<!--对话框的标题-->
<v-toolbar dense dark color="primary">
<v-toolbar-title>{
{isEdit ? '修改' : '新增'}}品牌</v-toolbar-title>
<v-spacer/>
<!--关闭窗口的按钮-->
<v-btn icon @click="closeWindow"><v-icon>close</v-icon></v-btn>
</v-toolbar>
<!--对话框的内容,表单 这里是要把获得的brand 数据传递给子组件,使用自定义标签::oldBrand 而父组件值为oldBrand-->
<v-card-text class="px-5" style="height:400px">
<brand-form @close="closeWindow" :oldBrand="oldBrand" :isEdit="isEdit"/>
</v-card-text>
</v-card>
</v-dialog>
</v-card>
</template>
<script>
// 导入自定义的表单组件,引入子组件 brandform
import BrandForm from './BrandForm'
export default {
name: "brand",
data() {
return {
search: '', // 搜索过滤字段
totalBrands: 0, // 总条数
brands: [], // 当前页品牌数据
loading: true, // 是否在加载中
pagination: {}, // 分页信息
headers: [
{text: 'id', align: 'center', value: 'id'},
{text: '名称', align: 'center', sortable: false, value: 'name'},
{text: 'LOGO', align: 'center', sortable: false, value: 'image'},
{text: '首字母', align: 'center', value: 'letter', sortable: true,},
{text: '操作', align: 'center', value: 'id', sortable: false}
],
show: false,// 控制对话框的显示
oldBrand: {}, // 即将被编辑的品牌数据
isEdit: false, // 是否是编辑
}
},
mounted() { // 渲染后执行
// 查询数据--搜索后页面还处在第几页,只要搜索,页面渲染后重新查询
this.getDataFromServer();
},
watch: {
pagination: { // 监视pagination属性的变化
deep: true, // deep为true,会监视pagination的属性及属性中的对象属性变化
handler() {
// 变化后的回调函数,这里我们再次调用getDataFromServer即可
this.getDataFromServer();
}
},
search: { // 监视搜索字段
handler() {
this.pagination.page =1;
this.getDataFromServer();
}
}
},
methods: {
getDataFromServer() { // 从服务的加载数的方法。
// 发起请求
this.$http.get("/item/brand/page", {
params: {
key: this.search, // 搜索条件
page: this.pagination.page,// 当前页
rows: this.pagination.rowsPerPage,// 每页大小
sortBy: this.pagination.sortBy,// 排序字段
desc: this.pagination.descending// 是否降序
}
}).then(resp => { // 这里使用箭头函数
this.brands = resp.data.items;
this.totalBrands = resp.data.total;
// 完成赋值后,把加载状态赋值为false
this.loading = false;
//
})
},
addBrand() {
// 修改标记,新增前修改为false
this.isEdit = false;
// 控制弹窗可见:
this.show = true;
// 把oldBrand变为null,因为之前打开过修改窗口,oldBrand数据被带过来了,导致新增
this.oldBrand = null;
},
editBrand(oldBrand){
//test 使用
//this.show = true;
//获取要编辑的brand
//this.oldBrand = oldBrand;
//requestParam,相当于把http,url ?name=zhangsan&age=21 传给方法
//pathvarable 相当与把url www.emporium.com/1/2 传给方法
//如果不需要url上的参数controller不需要绑定数据
// 根据品牌信息查询商品分类, 因为前台页面请求是拼接的, data 类似于jquery 里面回显的数据
this.$http.get("/item/category/bid/" + oldBrand.id)
.then(({data}) => {
// 修改标记
this.isEdit = true;
// 控制弹窗可见:
this.show = true;
// 获取要编辑的brand
this.oldBrand = oldBrand
// 回显商品分类
this.oldBrand.categories = data;
})
},
closeWindow(){
// 重新加载数据
this.getDataFromServer();
// 关闭窗口
this.show = false;
}
},
components:{
BrandForm
}
}
</script>
<style scoped>
</style>
Let's start writing the background logic
Start writing the background, first write a product category
Brand.java
package com.leyou.item.pojo;
import lombok.Data;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
/**
* Created by Administrator on 2023/9/2.
*/
@Data
@Table(name="tb_brand")
public class Brand {
@Id
@GeneratedValue(strategy= GenerationType.IDENTITY)
private Long id;
private String name;//品牌名称
private String image;//品牌图片
private Character letter;
}
Next we write our general Mapper class
Let's write the service interface
Let's write the Controller class
analyze
What is returned: the data of the current page (list collection) and the total number of items
That is, the above returns the following paging object. In the ly-common module, if we need to use the object of this module, then we need to import this module as a dependency into another module.
Here is the module ly-item-service under ly-item that needs to use the PageResult object
The following is the code in the Controller
Let's complete the method in Service
package com.leyou.item.service;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
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 tk.mybatis.mapper.entity.Example;
/**
* Created by Administrator on 2023/9/2.
*/
@Service
public class BrandService {
//内部需要一个mapper调用
@Autowired
private BrandMapper brandMapper;
/**
*
* @param page 当前页
* @param rows 每页大小
* @param sortBy 排序字段
* @param desc 是否降序
* @param key 搜索关键字
* @return
*/
public PageResult<Brand> queryBrandByPageAndSort(Integer page,Integer rows, String sortBy, Boolean desc, String key) {
//开启分页
//这个会自动拼接到后面的sql语句上面
PageHelper.startPage(page,rows);//传进来一个页码和展示多少行的数据,
//过滤
Example example = new Example(Brand.class);
if(key != null && !"".equals(key)) {
//进来有一模糊查询
//把这个语句拼接上
example.createCriteria().andLike("name","%" + key + "%").orEqualTo("letter",key);
}
if(sortBy != null && !"".equals(sortBy)) {
//根据sortBy字段进行排序
String orderByClause = sortBy + (desc ? " DESC " : " ASC ");
example.setOrderByClause(orderByClause);
}
//利用通用mapper进行查询
Page<Brand> pageInfo = (Page<Brand>) brandMapper.selectByExample(example);
//返回结果
//这里面传递总条数和页面信息
return new PageResult<>(pageInfo.getTotal(),pageInfo);
}
}
Let me tell you, when using Autowired to inject Mapper, it prompts that it cannot be injected, and it becomes popular