5.实现商品分类查询

商城的核心自然是商品,而商品多了以后,肯定要进行分类,并且不同的商品会有不同的品牌信息,其关系如图所示:

商城项目-实现商品分类查询_商城


  • 一个商品分类下有很多商品
  • 一个商品分类下有很多品牌
  • 而一个品牌,可能属于不同的分类
  • 一个品牌下也会有很多商品

因此,我们需要依次去完成:商品分类、品牌、商品的开发。

5.1.导入数据

首先导入SQL

文档:商城SQL.note

链接:http://note.youdao.com/noteshare?id=b41558310d69342554f1fe8f80c977b2&sub=C10B736123364057AB0676C328A9F56F

我们先看商品分类表:

商城项目-实现商品分类查询_父节点_02

CREATE TABLE `tb_category` (
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '类目id',
`name` varchar(20) NOT NULL COMMENT '类目名称',
`parent_id` bigint(20) NOT NULL COMMENT '父类目id,顶级类目填0',
`is_parent` tinyint(1) NOT NULL COMMENT '是否为父节点,0为否,1为是',
`sort` int(4) NOT NULL COMMENT '排序指数,越小越靠前',
PRIMARY KEY (`id`),
KEY `key_parent_id` (`parent_id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1424 DEFAULT CHARSET=utf8 COMMENT='商品类目表,类目和商品(spu)是一对多关系,类目与品牌是多对多关系';

因为商品分类会有层级关系,因此这里我们加入了​​parent_id​​字段,对本表中的其它分类进行自关联。

5.2.页面实现

5.2.1.页面分析

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

商城项目-实现商品分类查询_sed_03

商品分类之间是会有层级关系的,采用树结构去展示是最直观的方式。

一起来看页面,对应的是/pages/item/Category.vue:

商城项目-实现商品分类查询_商城_04

页面模板:

<template>
<v-card>
<v-flex xs12 sm10>
<v-tree url="/item/category/list"
:treeData="treeData"
:isEdit="isEdit"
@handleAdd="handleAdd"
@handleEdit="handleEdit"
@handleDelete="handleDelete"
@handleClick="handleClick"
/>
</v-flex>
</v-card>
</template>
  • ​v-card​​:卡片,是vuetify中提供的组件,提供一个悬浮效果的面板,一般用来展示一组数据。

商城项目-实现商品分类查询_商城_05

  • ​v-flex​​:布局容器,用来控制响应式布局。与BootStrap的栅格系统类似,整个屏幕被分为12格。我们可以控制所占的格数来控制宽度:

商城项目-实现商品分类查询_商城_06

本例中,我们用​​sm10​​控制在小屏幕及以上时,显示宽度为10格

  • ​v-tree​​:树组件。Vuetify并没有提供树组件,这个是我们自己编写的自定义组件:

商城项目-实现商品分类查询_实现商品分类查询_07

里面涉及一些vue的高级用法,大家暂时不要关注其源码,会用即可。

5.2.2.树组件的用法

也可参考课前资料中的:《自定义Vue组件的用法.md》

这里我贴出树组件的用法指南。


属性列表:


属性名称

说明

数据类型

默认值

url

用来加载数据的地址,即延迟加载

String

-

isEdit

是否开启树的编辑功能

boolean

false

treeData

整颗树数据,这样就不用远程加载了

Array

-

这里推荐使用url进行延迟加载,​每当点击父节点时,就会发起请求,根据父节点id查询子节点信息​。

当有treeData属性时,就不会触发url加载

远程请求返回的结果格式:

[
{
"id": 74,
"name": "手机",
"parentId": 0,
"isParent": true,
"sort": 2
},
{
"id": 75,
"name": "家用电器",
"parentId": 0,
"isParent": true,
"sort": 3
}
]


事件:


事件名称

说明

回调参数

handleAdd

新增节点时触发,isEdit为true时有效

新增节点node对象,包含属性:name、parentId和sort

handleEdit

当某个节点被编辑后触发,isEdit为true时有效

被编辑节点的id和name

handleDelete

当删除节点时触发,isEdit为true时有效

被删除节点的id

handleClick

点击某节点时触发

点击节点的node对象,包含完整的node信息


完整node的信息


回调函数中返回完整的node节点会包含以下数据:

{
"id": 76, // 节点id
"name": "手机", // 节点名称
"parentId": 75, // 父节点id
"isParent": false, // 是否是父节点
"sort": 1, // 顺序
"path": ["手机", "手机通讯", "手机"] // 所有父节点的名称数组
}

5.3.实现功能

5.3.1.url异步请求

给大家的页面中,treeData是假数据,我们删除数据treeData属性,只保留url看看会发生什么:

<v-tree url="/item/category/list"
:isEdit="isEdit"
@handleAdd="handleAdd"
@handleEdit="handleEdit"
@handleDelete="handleDelete"
@handleClick="handleClick"
/>

刷新页面,可以看到:

商城项目-实现商品分类查询_商城_08

页面中的树没有了,并且发起了一条请求:http://localhost/api/item/category/list?pid=0

大家可能会觉得很奇怪,我们明明是使用的相对路径,讲道理发起的请求地址应该是:

​​

但实际却是:

​​

这是因为,我们有一个全局的配置文件,对所有的请求路径进行了约定:

商城项目-实现商品分类查询_父节点_09

路径是localhost,并且默认加上了/api的前缀,这恰好与我们的网关设置匹配,我们只需要把地址改成网关的地址即可,因为我们使用了nginx反向代理,这里可以写域名。

接下来,我们要做的事情就是编写后台接口,返回对应的数据即可。

5.3.2.实体类

在​​ly-item-interface​​中添加category实体类:

商城项目-实现商品分类查询_商城_10

内容:

@Table(name="tb_category")
public class Category {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
private String name;
private Long parentId;
private Boolean isParent; // 注意isParent生成的getter和setter方法需要手动加上Is
private Integer sort;
// getter和setter略
}

需要注意的是,这里要用到jpa的注解,因此我们在​​ly-item-iterface​​中添加jpa依赖

<dependency>
<groupId>javax.persistence</groupId>
<artifactId>persistence-api</artifactId>
<version>1.0</version>
</dependency>

5.3.3.controller

编写一个controller一般需要知道四个内容:


  • 请求方式:决定我们用GetMapping还是PostMapping
  • 请求路径:决定映射路径
  • 请求参数:决定方法的参数
  • 返回值结果:决定方法的返回值

在刚才页面发起的请求中,我们就能得到绝大多数信息:

商城项目-实现商品分类查询_类目_11


  • 请求方式:Get
  • 请求路径:/api/item/category/list。其中/api是网关前缀,/item是网关的路由映射,真实的路径应该是/category/list
  • 请求参数:pid=0,根据tree组件的说明,应该是父节点的id,第一次查询为0,那就是查询一级类目
  • 返回结果:??
    根据前面tree组件的用法我们知道,返回的应该是json数组:
[
{
"id": 74,
"name": "手机",
"parentId": 0,
"isParent": true,
"sort": 2
},
{
"id": 75,
"name": "家用电器",
"parentId": 0,
"isParent": true,
"sort": 3
}
]
  • ​对应的java类型可以是List集合,里面的元素就是类目对象了。也就是​​List<Category>​

添加Controller:

商城项目-实现商品分类查询_商城_12

controller代码:

@Controller
@RequestMapping("category")
public class CategoryController {

@Autowired
private CategoryService categoryService;

/**
* 根据parentId查询类目
* @param pid
* @return
*/
@RequestMapping("list")
public ResponseEntity<List<Category>> queryCategoryListByParentId(@RequestParam(value = "pid", defaultValue = "0") Long pid) {
try {
if (pid == null || pid.longValue() < 0){
// pid为null或者小于等于0,响应400
return ResponseEntity.badRequest().build();
}
// 执行查询操作
List<Category> categoryList = this.categoryService.queryCategoryListByParentId(pid);
if (CollectionUtils.isEmpty(categoryList)){
// 返回结果集为空,响应404
return ResponseEntity.notFound().build();
}
// 响应200
return ResponseEntity.ok(categoryList);
} catch (Exception e) {
e.printStackTrace();
}
// 响应500
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();
}
}

5.3.4.service

一般service层我们会定义接口和实现类,不过这里我们就偷懒一下,直接写实现类了:

商城项目-实现商品分类查询_sed_13

@Service
public class CategoryService {

@Autowired
private CategoryMapper categoryMapper;

/**
* 根据parentId查询子类目
* @param pid
* @return
*/
public List<Category> queryCategoryListByParentId(Long pid) {
Category record = new Category();
record.setParentId(pid);
return this.categoryMapper.select(record);
}
}

5.3.5.mapper

我们使用通用mapper来简化开发:

public interface CategoryMapper extends Mapper<Category> {
}

要注意,我们并没有在mapper接口上声明@Mapper注解,那么mybatis如何才能找到接口呢?

我们在启动类上添加一个扫描包功能:

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.leyou.item.mapper") // mapper接口的包扫描
public class LeyouItemServiceApplication {

public static void main(String[] args) {
SpringApplication.run(LeyouItemServiceApplication.class, args);
}
}

5.3.6.启动并测试

我们不经过网关,直接访问:http://localhost:8081/category/list

商城项目-实现商品分类查询_类目_14

然后试试网关是否畅通:http://api.leyou.com/api/item/category/list

商城项目-实现商品分类查询_父节点_15

一切OK!

然后刷新后台管理页面查看:

商城项目-实现商品分类查询_实现商品分类查询_16

发现报错了!

浏览器直接访问没事,但是这里却报错,什么原因?

请看下一篇 跨域问题