谷粒商城分布式基础(八)—— 商品服务API—属性分组(父子组件 & 级联选择器)

 


一、基本概念

复制代码
  SPU:standard product unit(标准化产品单元):是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。如iphoneX是SPU
  
  SKU:stock keeping unit(库存量单位):库存进出计量的基本单元,可以是件
/盒/托盘等单位。SKU是对于大型连锁超市DC配送中心物流管理的一个必要的方法。
现在已经被引申为产品统一编号的简称,每种产品对应有唯一的SKU号。如iphoneX 64G 黑色 是SKU   同一个SPU拥有的特性叫基本属性。如机身长度,这个是手机共用的属性。而每款手机的属性值不同   能决定库存量的叫销售属性。如颜色   基本属性[规格参数]与销售属性
  每个分类下的商品共享规格参数,与销售属性。只是有些商品不一定要用这个分类下全部的属性;   属性是以三级分类组织起来的   规格参数中有些是可以提供检索的   规格参数也是基本属性,他们具有自己的分组   属性的分组也是以三级分类组织起来的   属性名确定的,但是值是每一个商品不同来决定的
复制代码

二、数据库设计

1、设计结构图

复制代码


spu—sku属性表

  荣耀V20有两个属性,网络和像素,但是这两个属性的spu是同一个,代表是同款手机。

  sku表里保存spu是同一手机,sku可能相同可能不同,相同代表是同一款,不同代表是不同款。

  属性表说明每个属性的 枚举值

  分类表有所有的分类,但有父子关系

复制代码

2、sql脚本

为了方便后面开发,我们就不再一一创建菜单目录了,将已经创建好的sql脚本导入,gulimall_admin数据库运行sql脚本
导入完毕之后的效果

三、接口API文档

接口文档地址 https://easydoc.xyz/s/78237135

四、属性分组

1、前端组件抽取 & 父子组件交互

属性分组
现在想要实现点击菜单的左边,能够实现在右边展示数据

 根据请求地址: http://localhost:8001/#/product-attrgroup

 所以应该有product/attrgroup.vue。我们之前写过product/cateory.vue,现在我们 要抽象到common//cateory.vue

复制代码
1)左侧内容
  要在左面显示菜单,右面显示表格
  新建product/attrgroup,复制<el-row :gutter="20">,放到attrgroup.vue的<template>。20表示列间距

   去element-ui文档里找到布局,
   <el-row :gutter="20">
     <el-col :span="6"><div class="grid-content bg-purple"></div></el-col>
     <el-col :span="18"><div class="grid-content bg-purple"></div></el-col>
   </el-row>
  

  有了布局之后,要在里面放内容。接下来要抽象一个分类vue。新建 common/category,生成vue模板。把之前写的el-tree放到<template>

  <el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeClick" ></el-tree>

  所以他把menus绑定到了菜单上, 所以我们应该在export default {中有menus的信息 该具体信息会随着点击等事件的发生会改变值(或比如created生命周期时), tree也就同步变化了

  

  common/category写好后,就可以在attrgroup.vue中导入使用了
  <script>
  import Category from "../common/category";
  export default {
    //import引入的组件需要注入到对象中才能使用。组件名:自定义的名字,一致可以省略
  components: { Category},

  导入了之后,就可以在attrgroup.vue中找合适位置放好
  <template>
    <el-row :gutter="20">
    <el-col :span="6">
      <category @tree-node-click="treenodeclick"></category>
    </el-col>

复制代码
2)右侧表格内容
  开始填写属性分组页面右侧的表格 复制gulimall-product\src\main\resources\src\views\modules\product\attrgroup.vue中的部分内 容div到attrgroup.vue
  批量删除是弹窗add-or-update
  导入data、结合components
复制代码
3)父子组件
  要实现功能:点击左侧,右侧表格对应内容显示。
  父子组件传递数据:category.vue点击时,引用它的attgroup.vue能感知到, 然后通知到add-or-update
  比如嵌套div,里层div有事件后冒泡到外层div(是指一次点击调用了两个div的点击函数)

  子组件(category)给父组件(attrgroup)传递数据,事件机制;

   去element-ui的tree部分找event事件,看node-click()

   在category中绑定node-click事件,
   <el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeClick" >
   </el-tree>

   this.$emit()
   子组件给父组件发送一个事件,携带上数据;
   nodeClick(data,Node,component){
     console.log("子组件被点击",data,Node,component);
     this.$emit("tree-node-click",data,Node,component);
   },

   第一个参数事件名字随便写, 后面可以写任意多的东西,事件发生时都会传出去:this.$emit(事件名,“携带的数据”);

   父组件中的获取发送的事件:在attr-group中写 <category @tree-node-click="treeNodeClick"></category> 表明他的子组件可能会传递过来点击事件,用自定义的函数接收传递过来的参数

   

   父组件中进行处理
   //获取发送的事件数据
   treeNodeClick(data,Node,component){
     console.log("attgroup感知到的category的节点被点击",data,Node,component);
     console.log("刚才被点击的菜单ID",data.catId);
   },

复制代码

/common/category.vue

复制代码
<template>
  <el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeClick">
  </el-tree>
</template>

<script>
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json 文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';

export default {
  //import 引入的组件需要注入到对象中才能使用
  components: {},
  props: {},
  data() {
    //这里存放数据
    return {
      menus: [],
      expandedKey: [],
      defaultProps: {
        children: "children",
        label: "name",
      },
    };
  },
  //计算属性 类似于 data 概念
  computed: {},
  //监控 data 中的数据变化
  watch: {},
  //方法集合
  methods: {
    getMenus() {
      this.$http({
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get",
      }).then(({ data }) => {
        console.log("成功获取到菜单数据...", data.data);
        this.menus = data.data;
      });
    },
    nodeClick(data,node,component){
        console.log("子组件category的节点被点击",data,node,component);
        //向父组件发送事件;
        this.$emit("tree-node-click",data,node,component);
    }
  },
  //生命周期 - 创建完成(可以访问当前 this 实例)
  created() {
      this.getMenus();
  },
  //生命周期 - 挂载完成(可以访问 DOM 元素)
  mounted() {},
  beforeCreate() {}, //生命周期 - 创建之前
  beforeMount() {}, //生命周期 - 挂载之前
  beforeUpdate() {}, //生命周期 - 更新之前
  updated() {}, //生命周期 - 更新之后
  beforeDestroy() {}, //生命周期 - 销毁之前
  destroyed() {}, //生命周期 - 销毁完成
  activated() {}, //如果页面有 keep-alive 缓存功能,这个函数会触发
};
</script>
<style lang='scss' scoped>
//@import url(); 引入公共 css 类
</style>
复制代码

/product/attrgroup.vue

复制代码
<template>
  <el-row :gutter="20">
    <el-col :span="6"> <category @tree-node-click="treenodeclick"></category></el-col>      
    <el-col :span="18"
      ><div class="mod-config">
        <el-form
          :inline="true"
          :model="dataForm"
          @keyup.enter.native="getDataList()"
        >
          <el-form-item>
            <el-input
              v-model="dataForm.key"
              placeholder="参数名"
              clearable
            ></el-input>
          </el-form-item>
          <el-form-item>
            <el-button @click="getDataList()">查询</el-button>
            <el-button
              v-if="isAuth('product:attrgroup:save')"
              type="primary"
              @click="addOrUpdateHandle()"
              >新增</el-button
            >
            <el-button
              v-if="isAuth('product:attrgroup:delete')"
              type="danger"
              @click="deleteHandle()"
              :disabled="dataListSelections.length <= 0"
              >批量删除</el-button
            >
          </el-form-item>
        </el-form>
        <el-table
          :data="dataList"
          border
          v-loading="dataListLoading"
          @selection-change="selectionChangeHandle"
          style="width: 100%"
        >
          <el-table-column
            type="selection"
            header-align="center"
            align="center"
            width="50"
          >
          </el-table-column>
          <el-table-column
            prop="attrGroupId"
            header-align="center"
            align="center"
            label="分组id"
          >
          </el-table-column>
          <el-table-column
            prop="attrGroupName"
            header-align="center"
            align="center"
            label="组名"
          >
          </el-table-column>
          <el-table-column
            prop="sort"
            header-align="center"
            align="center"
            label="排序"
          >
          </el-table-column>
          <el-table-column
            prop="descript"
            header-align="center"
            align="center"
            label="描述"
          >
          </el-table-column>
          <el-table-column
            prop="icon"
            header-align="center"
            align="center"
            label="组图标"
          >
          </el-table-column>
          <el-table-column
            prop="catelogId"
            header-align="center"
            align="center"
            label="所属分类id"
          >
          </el-table-column>
          <el-table-column
            fixed="right"
            header-align="center"
            align="center"
            width="150"
            label="操作"
          >
            <template slot-scope="scope">
              <el-button
                type="text"
                size="small"
                @click="addOrUpdateHandle(scope.row.attrGroupId)"
                >修改</el-button
              >
              <el-button
                type="text"
                size="small"
                @click="deleteHandle(scope.row.attrGroupId)"
                >删除</el-button
              >
            </template>
          </el-table-column>
        </el-table>
        <el-pagination
          @size-change="sizeChangeHandle"
          @current-change="currentChangeHandle"
          :current-page="pageIndex"
          :page-sizes="[10, 20, 50, 100]"
          :page-size="pageSize"
          :total="totalPage"
          layout="total, sizes, prev, pager, next, jumper"
        >
        </el-pagination>
        <!-- 弹窗, 新增 / 修改 -->
        <add-or-update
          v-if="addOrUpdateVisible"
          ref="addOrUpdate"
          @refreshDataList="getDataList"
        ></add-or-update></div
    ></el-col>
  </el-row>
</template>

<script>
/**
 * 父子组件传递数据
 * 1)子组件给父组件传递数据:事件机制;
 *   子组件给父组件发送一个事件,携带上数据
 *   this.$emit("事件名","携带的数据....")
 */
//这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json 文件,图片文件等等)
//例如:import 《组件名称》 from '《组件路径》';
import Category from "../common/category.vue";
import AddOrUpdate from './attrgroup-add-or-update'

export default {
  //import 引入的组件需要注入到对象中才能使用
  components: { Category,AddOrUpdate },
  props: {},
  data() {
    return {
      dataForm: {
        key: "",
      },
      dataList: [],
      pageIndex: 1,
      pageSize: 10,
      totalPage: 0,
      dataListLoading: false,
      dataListSelections: [],
      addOrUpdateVisible: false,
    };
  },
  activated() {
    this.getDataList();
  },
  methods: {
    //感知树节点被点击
    treenodeclick(data,node,component){
        console.log("attrgroup感知到category的节点被点击",data,node,component);
        console.log("刚才被点击的菜单id:",data.catId)
    },
    // 获取数据列表
    getDataList() {
      this.dataListLoading = true;
      this.$http({
        url: this.$http.adornUrl("/product/attrgroup/list"),
        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;
      });
    },
    // 每页数
    sizeChangeHandle(val) {
      this.pageSize = val;
      this.pageIndex = 1;
      this.getDataList();
    },
    // 当前页
    currentChangeHandle(val) {
      this.pageIndex = val;
      this.getDataList();
    },
    // 多选
    selectionChangeHandle(val) {
      this.dataListSelections = val;
    },
    // 新增 / 修改
    addOrUpdateHandle(id) {
      this.addOrUpdateVisible = true;
      this.$nextTick(() => {
        this.$refs.addOrUpdate.init(id);
      });
    },
    // 删除
    deleteHandle(id) {
      var ids = id
        ? [id]
        : this.dataListSelections.map((item) => {
            return item.attrGroupId;
          });
      this.$confirm(
        `确定对[id=${ids.join(",")}]进行[${id ? "删除" : "批量删除"}]操作?`,
        "提示",
        {
          confirmButtonText: "确定",
          cancelButtonText: "取消",
          type: "warning",
        }
      ).then(() => {
        this.$http({
          url: this.$http.adornUrl("/product/attrgroup/delete"),
          method: "post",
          data: this.$http.adornData(ids, false),
        }).then(({ data }) => {
          if (data && data.code === 0) {
            this.$message({
              message: "操作成功",
              type: "success",
              duration: 1500,
              onClose: () => {
                this.getDataList();
              },
            });
          } else {
            this.$message.error(data.msg);
          }
        });
      });
    },
  },
};
</script>
<style lang='scss' scoped>
//@import url(); 引入公共 css 类
</style>
复制代码

/product/attrgroup-add-or-update.vue

复制代码
<template>
  <el-dialog
    :title="!dataForm.attrGroupId ? '新增' : '修改'"
    :close-on-click-modal="false"
    :visible.sync="visible">
    <el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()" label-width="80px">
    <el-form-item label="组名" prop="attrGroupName">
      <el-input v-model="dataForm.attrGroupName" placeholder="组名"></el-input>
    </el-form-item>
    <el-form-item label="排序" prop="sort">
      <el-input v-model="dataForm.sort" placeholder="排序"></el-input>
    </el-form-item>
    <el-form-item label="描述" prop="descript">
      <el-input v-model="dataForm.descript" placeholder="描述"></el-input>
    </el-form-item>
    <el-form-item label="组图标" prop="icon">
      <el-input v-model="dataForm.icon" placeholder="组图标"></el-input>
    </el-form-item>
    <el-form-item label="所属分类id" prop="catelogId">
      <el-input v-model="dataForm.catelogId" placeholder="所属分类id"></el-input>
    </el-form-item>
    </el-form>
    <span slot="footer" class="dialog-footer">
      <el-button @click="visible = false">取消</el-button>
      <el-button type="primary" @click="dataFormSubmit()">确定</el-button>
    </span>
  </el-dialog>
</template>

<script>
  export default {
    data () {
      return {
        visible: false,
        dataForm: {
          attrGroupId: 0,
          attrGroupName: '',
          sort: '',
          descript: '',
          icon: '',
          catelogId: ''
        },
        dataRule: {
          attrGroupName: [
            { required: true, message: '组名不能为空', trigger: 'blur' }
          ],
          sort: [
            { required: true, message: '排序不能为空', trigger: 'blur' }
          ],
          descript: [
            { required: true, message: '描述不能为空', trigger: 'blur' }
          ],
          icon: [
            { required: true, message: '组图标不能为空', trigger: 'blur' }
          ],
          catelogId: [
            { required: true, message: '所属分类id不能为空', trigger: 'blur' }
          ]
        }
      }
    },
    methods: {
      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()
            }).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
              }
            })
          }
        })
      },
      // 表单提交
      dataFormSubmit () {
        this.$refs['dataForm'].validate((valid) => {
          if (valid) {
            this.$http({
              url: this.$http.adornUrl(`/product/attrgroup/${!this.dataForm.attrGroupId ? 'save' : 'update'}`),
              method: 'post',
              data: this.$http.adornData({
                'attrGroupId': this.dataForm.attrGroupId || undefined,
                'attrGroupName': this.dataForm.attrGroupName,
                'sort': this.dataForm.sort,
                'descript': this.dataForm.descript,
                'icon': this.dataForm.icon,
                'catelogId': this.dataForm.catelogId
              })
            }).then(({data}) => {
              if (data && data.code === 0) {
                this.$message({
                  message: '操作成功',
                  type: 'success',
                  duration: 1500,
                  onClose: () => {
                    this.visible = false
                    this.$emit('refreshDataList')
                  }
                })
              } else {
                this.$message.error(data.msg)
              }
            })
          }
        })
      }
    }
  }
</script>
复制代码

2、获取分类属性分组

复制代码
https://easydoc.xyz/s/78237135/ZUqEdvA4/OXTgKobR

查询功能:
GET /product/attrgroup/list/{catelogId}

(1)按照这个url,去gulimall-product项目下的com.atguigu.gulimall.product.controller.AttrGroupController里修改
/**
* 分页查询分类属性分组
*/
@RequestMapping("/list/{catelogId}")
//@RequiresPermissions("product:attrgroup:list")
public R list(@RequestParam Map<String, Object> params, @PathVariable("catelogId")Long catelogId){
//PageUtils page = attrGroupService.queryPage(params);
PageUtils page = attrGroupService.queryPage(params,catelogId);
return R.ok().put("page", page);
}
(2)增加接口与实现
/**
* 分类查询分类属性分组
* @param params
* @param catelogId
* @return
*/
@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<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>().eq("catelog_id",catelogId);
if(!StringUtils.isEmpty(key)){
wrapper.and((obj)->{
obj.eq("attr_group_id", key).or().like("attr_group_name",key);
});
}
IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
return new PageUtils(page);
}
}
(3)测试
  localhost:88/api/product/attrgroup/list/1

 (4)调整前端

    发送请求时url携带id信息,${this.catId},get参数携带page信息

    点击第3级分类时才查,修改attr-group.vue中的函数即可

  //感知树节点被点击
    treenodeclick(data,node,component){
        console.log("attrgroup感知到category的节点被点击",data,node,component);
        console.log("刚才被点击的菜单id:",data.catId);
        if(node.level == 3){
          this.catId = data.catId;
          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;
      });
    },
复制代码

3、分组新增 & 级联选择器

上面演示了查询功能,下面写insert分类
但是想要下面这个效果:

  下拉菜单应该是手机一级分类,这个功能是级联选择器 

复制代码
1)级联选择器<el-cascader>
  级联选择的下拉同样是个options数组,多级的话用children属性即可
  只需为 Cascader 的options属性指定选项数组即可渲染出一个级联选择器。通过 props.expandTrigger 可以定义展开子级菜单的触发方式

  去vue里找src\views\modules\product\attrgroup-add-or-update.vue
  修改对应的位置为<el-cascader 。。。>
  把data()里的数组categorys绑定到options上即可,更详细的设置可以用props绑定

  @JsonInclude去空字段
  优化:没有下级菜单时不要有下一级空菜单,在java端把children属性空值去掉,空集合时去掉字段,
  可以用@JsonInclude(Inlcude.NON_EMPTY)注解标注在实体类com.atguigu.gulimall.product.entity.BrandEntity的属性上,
  @TableField(exist =false)   @JsonInclude(JsonInclude.Include.NON_EMPTY)   private List<CategoryEntity> children;

  提交完后返回页面也刷新了,是用到了父子组件。在$message弹窗结束回调   $this.emit
复制代码

4、分组修改 & 级联选择器回显

复制代码
接下来要解决的问题是,修改了该vue后,新增是可以用,修改回显就有问题了,应该回显3级

<el-button type="text" size="small" @click="addOrUpdateHandle(scope.row.attrGroupId)">修改</el-button>
 
<script>  
  // 新增 / 修改
  addOrUpdateHandle(id) {
      // 先显示弹窗
      this.addOrUpdateVisible = true;
      // .$nextTick(代表渲染结束后再接着执行
      this.$nextTick(() => {
        // this是attrgroup.vue
        // $refs是它里面的所有组件。在本vue里使用的时候,标签里会些ref=""
        // addOrUpdate这个组件
        // 组件的init(id);方法
        this.$refs.addOrUpdate.init(id);
      });
    },
</script>
在init方法里进行回显
但是分类的id还是不对,应该是用数组封装的路径 

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

修改AttrGroupEntity
/**
* 三级分类修改的时候回显路径
*/
@TableField(exist = false)
private Long[] catelogPath;
修改controller
/**
* 信息
*/
@RequestMapping("/info/{attrGroupId}")
//@RequiresPermissions("product:attrgroup:info")
public R info(@PathVariable("attrGroupId") Long attrGroupId){
   AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);
   // 用当前当前分类id查询完整路径并写入 attrGroup
attrGroup.setCatelogPath(categoryService.findCatelogPath(attrGroup.getCatelogId()));
return R.ok().put("attrGroup", attrGroup);
}
添加service
/**
* 找到catelogId的完整路径
* @param catelogId
* @return
*/
@Override
public Long[] findCatelogPath(Long catelogId) {
List<Long> paths = new ArrayList<>();
List<Long> parentPath = findParentPath(catelogId, paths);
Collections.reverse(parentPath);
return (Long[])parentPath.toArray(new Long[parentPath.size()]);
}
/**
* 递归收集所有父节点
* @param catelogId
* @param paths
* @return
*/
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;
}
优化:会话关闭时清空内容,防止下次开启还遗留数据

复制代码

5、品牌分类关联 与 级联更新

  恢复分类原始数据

复制代码
1)处理分页问题
  分页统计数据出错,因为我们使用 mybatis-plus,要分页需要使用分页插件
  https://baomidou.com/guide/page.html

  解决办法:创建 mybatis 的配置

package com.atguigu.gulimall.product.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import org.apache.ibatis.annotations.Mapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@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);
// 开启 count join 优化,只针对部分 left join
paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
return paginationInterceptor;
}
}

  效果:

复制代码
复制代码
2)处理模糊查询问题

  修改com.atguigu.gulimall.product.service.impl.BrandServiceImpl的queryPage方法

@Override
public PageUtils queryPage(Map<String, Object> params) {
//1、获取key
String key = (String) params.get("key");
log.info("key的值:"+key);
//2、检索条件
QueryWrapper<BrandEntity> queryWrapper = new QueryWrapper<>();
if(!StringUtils.isEmpty(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);
}

  测试结果:

复制代码
复制代码
3)获取品牌关联的分类
com.atguigu.gulimall.product.controller.CategoryBrandRelationController 新增方法:
/**
* 获取当前品牌关联的所有分类列表
* @param brandId
* @return
*/
//@RequestMapping(value = "/catelog/list", method = RequestMethod.GET)
@GetMapping("/catelog/list")
//@RequiresPermissions("product:categorybrandrelation:list")
public R categorylist(@RequestParam("brandId")Long brandId){
List<CategoryBrandRelationEntity> data = categoryBrandRelationService.list(
new QueryWrapper<CategoryBrandRelationEntity>().eq("brand_id", brandId)
);
return R.ok().put("data", data);
}
复制代码
复制代码
4)新增品牌与分类关联关系
  修改com.atguigu.gulimall.product.controller.CategoryBrandRelationController的save方法
/**
* 新增品牌与分类关联关系
* @param categoryBrandRelation
* @return
*/
@RequestMapping("/save")
//@RequiresPermissions("product:categorybrandrelation:save")
public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){
categoryBrandRelationService.saveDetail(categoryBrandRelation);
//categoryBrandRelationService.save(categoryBrandRelation);
return R.ok();
}
   新增接口和实现类
/**
* 新增品牌与分类关联关系
* @param categoryBrandRelation
*/
@Override
public void saveDetail(CategoryBrandRelationEntity categoryBrandRelation) {
Long brandId = categoryBrandRelation.getBrandId();
Long catelogId = categoryBrandRelation.getCatelogId();
//1、查询详细名字
BrandEntity brandEntity = brandDao.selectById(brandId);
CategoryEntity categoryEntity = categoryDao.selectById(catelogId);

categoryBrandRelation.setBrandName(brandEntity.getName());
categoryBrandRelation.setCatelogName(categoryEntity.getName());

this.save(categoryBrandRelation);
}
  效果
复制代码
复制代码
5)解决数据存储冗余问题
  我们把品牌名称和分类名存储到了 品牌-分类关联表(pms_category_brand_relation)中,在数据设计中用冗余换效率

  存在问题:修改品牌和分类的时候,应该把关联表也修改

 (a)修改品牌名称冗余问题

  com.atguigu.gulimall.product.controller.BrandController 修改 update 方法

/**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:brand:update")
public R update(@Validated(UpdateGroup.class) @RequestBody BrandEntity brand){
brandService.updateDetail(brand);
//brandService.updateById(brand);
return R.ok();
}
 com.atguigu.gulimall.product.service.impl.BrandServiceImpl 新增接口和实现类
/**
* 更新品牌信息
* @param brand
*/
@Override
@Transactional
public void updateDetail(BrandEntity brand) {
//保证冗余字段的数据一致
this.updateById(brand);
if(!StringUtils.isEmpty(brand.getName())){
//同步更新其他关联表中的数据
categoryBrandRelationService.updateBrand(brand.getBrandId(), brand.getName());

//TODO 更新其他关联
}
}
 com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl 新增接口和实现类
/**
* 更新品牌关联信息
* @param brandId
* @param name
*/
@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));
}

 (b)修改分类名称冗余问题

  com.atguigu.gulimall.product.controller.CategoryController 修改 update 方法

  /**
* 修改
*/
@RequestMapping("/update")
//@RequiresPermissions("product:category:update")
public R update(@RequestBody CategoryEntity category){
categoryService.updateCascade(category);
    //categoryService.updateById(category);
return R.ok();
}
  
com.atguigu.gulimall.product.service.impl.CategoryServiceImpl 新增接口和实现类
/**
* 级联更新所有关联的数据
* @param category
*/
@Override
@Transactional
public void updateCascade(CategoryEntity category) {
this.updateById(category);
categoryBrandRelationService.updateCategory(category.getCatId(), category.getName());
}

com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl 新增接口和实现类
/**
* 更新关联表的分类名称
* @param catId
* @param name
*/
@Override
public void updateCategory(Long catId, String name) {
this.baseMapper.updateCategory(catId, name);
}

com.atguigu.gulimall.product.dao.CategoryBrandRelationDao 新增接口方法
 /**
 * 更新分类名称
* @param catId
* @param name
*/
void updateCategory(@Param("catId") Long catId, @Param("name") String name);

 CategoryBrandRelationDao.xml 新增对应xml方法
<!--更新关联分类的名称-->
<update id="updateCategory">
update pms_category_brand_relation set catelog_name=#{name} WHERE catelog_id=#{catId}
</update>
复制代码

 

posted @   沧海一粟hr  阅读(711)  评论(0编辑  收藏  举报
编辑推荐:
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
阅读排行:
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 字符编码:从基础到乱码解决
点击右上角即可分享
微信分享提示

目录导航