SPU:就是所有具体的统称(集合);
SKU:是每一个具体(集合中的元素)。
可能是一个SPU中含有多个SKU,然后某一个SKU(这个SKU就又叫SPU)下面又有很多个SKU
(有用) 接口文档地址:https://easydoc.xyz/s/78237135/ZUqEdvA4/
将项目提供的sys_menus.sql文件,放在gulimall_admin数据库的sys_menu表中执行,然后刷新页面
父子组件交互需要用到这个方法this.$emit(参数1,参数2,参数3)
① 因为三级分类这个组件,我们在其他地方也有用到,因此将其抽取到一个公共组件中,新建src\views\modules\common**\category.vue**
② 在src\views\modules\product**\attrgroup.vue**中导入公共组件(自定义的category标签),并使用:
import Category from "../common/category";
export default {
components: {
Category,
},
}
③ 在vue实例中使用自定义的category标签:
<el-col :span="6"><category>category>el-col>
2、父子组件:
① 在category.vue组件的
标签中绑定一个事件 @node-click:
<el-tree
:data="menus"
:props="defaultProps"
node-key="catId"
ref="menuTree"
@node-click="nodeclick">el-tree>
category.vue中定义nodeclick(data, node, component)方法,在该方法内向父组件发送事件,从而可以在父组件中使用:
nodeclick(data, node, component) {
console.log("子组件category的节点被点击", data, node, component);
//向父组件发送事件;
this.$emit("tree-node-click", data, node, component);
}
③ 在attrgroup.vue中使用这个category组件:(接收来自category.vue定义的子组件的信息)
<el-col :span="6"><category @tree-node-click="treenodeclick">category>el-col>
//感知树节点被点击
treenodeclick(data, node, component) {
console.log("attrgroup感知到category的节点被攻击:",data,node,component);
console.log("刚才被点解的菜单id:",data.catId);
},
需要用到 mybatis plus强大的条件构造器queryWrapper、updateWrapper:
查看前端API接口文档开发:https://easydoc.xyz/s/78237135/ZUqEdvA4/OXTgKobR
① 根据前端API文档可知,前端会发送一个get请求:/product/attrgroup/list/{catelogId},其中请求参数为:
{
page: 1,//当前页码
limit: 10,//每页记录数
sidx: 'id',//排序字段
order: 'asc/desc',//排序方式
key: '华为'//检索关键字
}
② 在com.atguigu.gulimall.product.controller.AttrGroupController类中定义一个方法,接收前端请求参数,请求路径为/product/attrgroup/list/{catelogId},请求方式为get请求:
@RestController
@RequestMapping("product/attrgroup")
public class AttrGroupController {
@Autowired
private AttrGroupService attrGroupService;
/**
* 列表
*/
@RequestMapping("/list/{catelogId}")
public R list(@RequestParam Map<String, Object> params,@PathVariable("catelogId")Long catelogId){
PageUtils page = attrGroupService.queryPage(params, catelogId);
return R.ok().put("page", page);
}
}
接口AttrGroupService
public interface AttrGroupService extends IService<AttrGroupEntity> {
PageUtils queryPage(Map<String, Object> params);
PageUtils queryPage(Map<String, Object> params, Long catelogId);
}
③ 在com.atguigu.gulimall.product.service.impl.AttrGroupServiceImpl 类中编写业务逻辑:
@Service("attrGroupService")
public class AttrGroupServiceImpl extends ServiceImpl<AttrGroupDao, AttrGroupEntity> implements AttrGroupService {
@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
if( catelogId == 0){
IPage<AttrGroupEntity> page = this.page(
//分页条件
new Query<AttrGroupEntity>().getPage(params),
//查询条件:无
new QueryWrapper<AttrGroupEntity>()
);
return new PageUtils(page);
}else {
String key = (String) params.get("key");
//select * from pms_attr_group where catelog_id=? and (attr_group_id=key or attr_group_name like %key%)
//QueryWrapper:构造查询条件
QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();
//查询条件1:数据库字段catelog_id的值与前端传入的参数catelogId相同,即catelog_id=catelogId
wrapper.eq("catelog_id",catelogId);
//查询条件2:字段 attr_group_id==key 或 字段 attr_group_name like %key%
if(!StringUtils.isEmpty(key)){
wrapper.and((obj)->{
obj.eq("attr_group_id",key).or().like("attr_group_name",key);
});
}
//根据分页条件params和查询条件wrapper进行查询
IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
return new PageUtils(page);
}
}
}
④ 使用postman测试:http://localhost:88/api/product/attrgroup/list/1?page=1&key=aa
⑤ 前后端联调:
//感知树节点被点击
treenodeclick(data, node, component) {
if (node.level == 3) {
this.catId = data.catId;
this.getDataList(); //重新查询
}
},
getAllDataList(){
this.catId = 0;
this.getDataList();
},
// 获取数据列表
getDataList() {
this.dataListLoading = true;
this.$http({
url: this.$http.adornUrl(`/product/attrgroup/list/${this.catId}`),
method: "get",
params: this.$http.adornParams({
page: this.pageIndex,
limit: this.pageSize,
key: this.dataForm.key
})
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataList = data.page.list;
this.totalPage = data.page.totalCount;
} else {
this.dataList = [];
this.totalPage = 0;
}
this.dataListLoading = false;
});
},
需求:点击新增属性分组的时候,填写所属分类时能够选择所属三级分类,并且三级分类后不显示其子组件空白页([]数组)
① 在attrgroup-add-or-update.vue文件中,加入elementui的级联选择器:el-cascader
<el-form-item label="所属分类id" prop="catelogId">
<el-cascader v-model="dataForm.catelogIds" :options="categorys" :props="props">
el-cascader>
el-form-item>
② 向后台请求所有分类数据:
getCategorys () {
this.$http({
url: this.$http.adornUrl("/product/category/list/tree"),
method: "get"
}).then(({ data }) => {
this.categorys = data.data
})
},
③ 在vue实例一创建的时候,就执行上面的方法:
created () {
this.getCategorys()
}
④ 数据data:
data () {
return {
props: {
value: "catId",
label: "name",
children: "children",
},
categorys: [],
visible: false,
//提交给数据库的数据
dataForm: {
attrGroupId: 0,
attrGroupName: '',
sort: '',
descript: '',
icon: '',
catelogIds: [],
catelogId: 0,
},
},
后端CategoryEntity实体
@JsonInclude(JsonInclude.Include.NON_EMPTY)
@TableField(exist=false)
private List<CategoryEntity> children;
需求:点击修改,回显三级分类(像上述新增一样回显)
① attrgroup-add-or-update.vue文件,在init()方法中,将catelogId的完整路径回显:
init (id) {
this.dataForm.attrGroupId = id || 0
this.visible = true
this.$nextTick(() => {
this.$refs['dataForm'].resetFields()
if (this.dataForm.attrGroupId) {
this.$http({
url: this.$http.adornUrl(`/product/attrgroup/info/${this.dataForm.attrGroupId}`),
method: 'get',
params: this.$http.adornParams()
//从数据库中查询出的数据data,将catelogId的完整路径回显出来
}).then(({ data }) => {
if (data && data.code === 0) {
this.dataForm.attrGroupName = data.attrGroup.attrGroupName
this.dataForm.sort = data.attrGroup.sort
this.dataForm.descript = data.attrGroup.descript
this.dataForm.icon = data.attrGroup.icon
this.dataForm.catelogId = data.attrGroup.catelogId
//查出catelogId的完整路径
this.dataForm.catelogPath = data.attrGroup.catelogPath
}
})
}
})
},
② 后端将catelogId的完整路径查询出来并响应给前端data:
首先,在AttrGroupEntity类中添加一个字段,然后将attrgroup响应给前端:
@TableField(exist = false)
private Long[] catelogPath;
在AttrGroupController类中编写一个方法,接受前端请求参数,调用Service逻辑,相应数据:
@RequestMapping("/info/{attrGroupId}")
public R info(@PathVariable("attrGroupId") Long attrGroupId){
AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);
Long catelogId = attrGroup.getCatelogId();
//编写findCatelogPath:查找当前节点的父系节点(递归查找 当前节点-->父节点-->父节点)
Long[] path = categoryService.findCatelogPath(catelogId);
attrGroup.setCatelogPath(path);
return R.ok().put("attrGroup", attrGroup);
}
③ 在CategoryServiceImpl类中递归收集三级菜单id:
//[2,25,225]
@Override
public Long[] findCatelogPath(Long catelogId) {
List<Long> paths = new ArrayList<>();
List<Long> parentPath = findParentPath(catelogId, paths);
Collections.reverse(parentPath);
return parentPath.toArray(new Long[parentPath.size()]);
}
//225,25,2
private List<Long> findParentPath(Long catelogId,List<Long> paths){
//1、收集当前节点id
paths.add(catelogId);
CategoryEntity byId = this.getById(catelogId);
if(byId.getParentCid()!=0){
findParentPath(byId.getParentCid(),paths);
}
return paths;
}
① 将分类维护的原始数据更新,将项目提供的pms_catelog.sql在pms_category表中执行
② 在品牌管理中,统计的分页数据是错的,因此引入mybatis-plus的分页插件:
后端:在com.atguigu.gulimall.product.config包下,配置分页插件即可:
@Configuration
@EnableTransactionManagement //开启事务
@MapperScan("com.atguigu.gulimall.product.dao")
public class MyBatisConfig {
//引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
// 设置请求的页面大于最大页后操作, true调回到首页,false 继续请求 默认false
paginationInterceptor.setOverflow(true);
// 设置最大单页限制数量,默认 500 条,-1 不受限制
paginationInterceptor.setLimit(1000);
return paginationInterceptor;
}
}
后端:在product–BrandServiceImpl类中修改queryPage()方法,增加模糊查询条件
@Override
public PageUtils queryPage(Map<String, Object> params) {
QueryWrapper<BrandEntity> queryWrapper = new QueryWrapper<>();
String key = (String)params.get("key");
if(!StringUtils.isEmpty(key)){
//brand_id=key 或者 name like %key%
queryWrapper.eq("brand_id",key).or().like("name",key);
}
IPage<BrandEntity> page = this.page(
new Query<BrandEntity>().getPage(params),queryWrapper);
return new PageUtils(page);
}
①给品牌管理增加一些有用数据,并将原来数据删除,同时将项目提供的前端代码中modules文件夹下的common和product文件夹替换掉renren-fast-vue\src\views\modules下的common和product文件夹。
②关联分类功能:一个品牌(小米)会关联多个分类(手机,电脑),一个分类(手机)会关联多个品牌(华为,小米),这样就属于多对多的关系,在数据库中一般就会有中间表:
参考前端接口API地址:https://easydoc.xyz/s/78237135/ZUqEdvA4/SxysgcEF
③请求地址为:/product/categorybrandrelation/catelog/list,请求方式为get,请求参数为brandId
获取当前品牌关联的所有分类列表:product–CategoryBrandRelationController
/**
* 获取当前品牌关联的所有分类列表
*/
@GetMapping("/catelog/list")
public R list(@RequestParam("brandId")Long brandId){
List<CategoryBrandRelationEntity> data = categoryBrandRelationService.list(
new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId));
return R.ok().put("page", data);
}
参考前端接口API地址:https://easydoc.xyz/s/78237135/ZUqEdvA4/7jWJki5e
① 请求地址为:product/categorybrandrelation/save,请求方式post,请求参数:{“brandId”:1,“catelogId”:2}
② 新增品牌与分类关联关系:
pms_category_brand_relation表:
③前端传来的参数包括brand_id和catelog_id,但是不包括brand_name和catelog_name,当新增关联时,数据库就不会保存brand_name和catelog_name,因此需要将他们查询出来,再保存数据到数据库。
在product–CategoryBrandRelationController类中:
@RequestMapping("/save")
public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
categoryBrandRelationService.saveDetail(categoryBrandRelation);
return R.ok();
}
在product–CategoryBrandRelationServiceImpl类中:(接口就省略了)
@Resource
private BrandDao brandDao;
@Resource
private CategoryDao categoryDao;
@Override
public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
Long brandId = categoryBrandRelation.getBrandId();
Long catelogId = categoryBrandRelation.getCatelogId();
//根据brandId和catelogId查询出详情
BrandEntity brandEntity = brandDao.selectById(brandId);
CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
categoryBrandRelation.setBrandName(brandEntity.getName());
categoryBrandRelation.setCatelogName(categoryEntity.getName());
this.save(categoryBrandRelation);
}
需求:当品牌管理中的品牌名和分类名修改的时候,关联分类中的品牌名和分类名也要跟着修改。
① 在BrandController类中修改update()方法:
@RequestMapping("/update")
public R update(@Validated(UpdateGroup.class)@RequestBody BrandEntity brand){
//updateDetail
brandService.updateDetail(brand);
return R.ok();
}
② 在BrandServiceImpl类中 :(其Service接口省略:接口方法updateDetail)
@Resource
private CategoryBrandRelationService categoryBrandRelationService;
@Transactional
@Override
public void updateDetail(BrandEntity brand) {
//保障冗余字段的数据一致
//更新品牌管理
this.updateById(brand);
if(!StringUtils.isEmpty(brand.getName())){
//同步更新其他关联数据:将品牌表的数据pms_brand更新到pms_category_brand_relation
categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());
}
}
③ 在CategoryBrandRelationService接口和CategoryBrandRelationServiceImpl实现类中:
接口实现上述updateBrand品牌方法,也实现上面saveDetail,后面updateCategory分类
public interface CategoryBrandRelationService extends IService<CategoryBrandRelationEntity> {
PageUtils queryPage(Map<String, Object> params);
void saveDetail(CategoryBrandRelationEntity categoryBrandRelation);
void updateBrand(Long brandId, String name);
void updateCategory(Long catId, String name);
}
CategoryBrandRelationServiceImpl实现类:updateBrand品牌方法
/**
* 将品牌表的数据pms_brand更新到品牌与分类关联表pms_category_brand_relation
* */
@Override
public void updateBrand(Long brandId, String name) {
CategoryBrandRelationEntity relationEntity = new CategoryBrandRelationEntity();
relationEntity.setBrandId(brandId);
relationEntity.setBrandName(name);
this.update(relationEntity,new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId));
}
① 在CategoryController类中修改update()方法:updateCascade
@RequestMapping("/update")
public R update(@RequestBody CategoryEntity category){
categoryService.updateCascade(category);
return R.ok();
}
② 在CategoryServiceImpl类中:(接口省略)
@Transactional
@Override
public void updateCascade(CategoryEntity category) {
this.updateById(category);
//更新关联分类:
categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
}
③ 在CategoryBrandRelationServiceImpl类中:updateCategory方法(自定义dao语句)
@Override
public void updateCategory(Long catId, String name) {
this.baseMapper.updateCategory(catId,name);
}
④dao层
/**
* 品牌分类关联
*
* @author jianghui
* @email [email protected]
* @date 2020-12-27 19:24:01
*/
@Mapper
public interface CategoryBrandRelationDao extends BaseMapper<CategoryBrandRelationEntity> {
void updateCategory(@Param("catId") Long catId, @Param("name") String name);
}
<mapper namespace="com.atguigu.gulimall.product.dao.CategoryBrandRelationDao">
<resultMap type="com.atguigu.gulimall.product.entity.CategoryBrandRelationEntity" id="categoryBrandRelationMap">
<result property="id" column="id"/>
<result property="brandId" column="brand_id"/>
<result property="catelogId" column="catelog_id"/>
<result property="brandName" column="brand_name"/>
<result property="catelogName" column="catelog_name"/>
resultMap>
<update id="updateCategory">
UPDATE `pms_category_brand_relation` SET catelog_name=#{name} WHERE catelog_id=#{catId}
update>
mapper>