Grain Mall - Basics - Commodity Service 4 - Attribute Grouping (P71-P74)


Commodity service - attribute grouping

1. Commodity service-API-attribute grouping

Interface document address
https://easydoc.xyz/s/78237135

1. Implement sys_menus.sqland improve the management menu

gulimall_admin/sys_menuExecute sql in the table

/*
SQLyog Ultimate v11.25 (64 bit)
MySQL - 5.7.27 : Database - gulimall_admin
*********************************************************************
*/


/*!40101 SET NAMES utf8 */;

/*!40101 SET SQL_MODE=''*/;

/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`gulimall_admin` /*!40100 DEFAULT CHARACTER SET utf8mb4 */;

USE `gulimall_admin`;

/*Table structure for table `sys_menu` */

DROP TABLE IF EXISTS `sys_menu`;

CREATE TABLE `sys_menu` (
  `menu_id` bigint(20) NOT NULL AUTO_INCREMENT,
  `parent_id` bigint(20) DEFAULT NULL COMMENT '父菜单ID,一级菜单为0',
  `name` varchar(50) DEFAULT NULL COMMENT '菜单名称',
  `url` varchar(200) DEFAULT NULL COMMENT '菜单URL',
  `perms` varchar(500) DEFAULT NULL COMMENT '授权(多个用逗号分隔,如:user:list,user:create)',
  `type` int(11) DEFAULT NULL COMMENT '类型   0:目录   1:菜单   2:按钮',
  `icon` varchar(50) DEFAULT NULL COMMENT '菜单图标',
  `order_num` int(11) DEFAULT NULL COMMENT '排序',
  PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=76 DEFAULT CHARSET=utf8mb4 COMMENT='菜单管理';

/*Data for the table `sys_menu` */

insert  into `sys_menu`(`menu_id`,`parent_id`,`name`,`url`,`perms`,`type`,`icon`,`order_num`) values (1,0,'系统管理',NULL,NULL,0,'system',0),(2,1,'管理员列表','sys/user',NULL,1,'admin',1),(3,1,'角色管理','sys/role',NULL,1,'role',2),(4,1,'菜单管理','sys/menu',NULL,1,'menu',3),(5,1,'SQL监控','http://localhost:8080/renren-fast/druid/sql.html',NULL,1,'sql',4),(6,1,'定时任务','job/schedule',NULL,1,'job',5),(7,6,'查看',NULL,'sys:schedule:list,sys:schedule:info',2,NULL,0),(8,6,'新增',NULL,'sys:schedule:save',2,NULL,0),(9,6,'修改',NULL,'sys:schedule:update',2,NULL,0),(10,6,'删除',NULL,'sys:schedule:delete',2,NULL,0),(11,6,'暂停',NULL,'sys:schedule:pause',2,NULL,0),(12,6,'恢复',NULL,'sys:schedule:resume',2,NULL,0),(13,6,'立即执行',NULL,'sys:schedule:run',2,NULL,0),(14,6,'日志列表',NULL,'sys:schedule:log',2,NULL,0),(15,2,'查看',NULL,'sys:user:list,sys:user:info',2,NULL,0),(16,2,'新增',NULL,'sys:user:save,sys:role:select',2,NULL,0),(17,2,'修改',NULL,'sys:user:update,sys:role:select',2,NULL,0),(18,2,'删除',NULL,'sys:user:delete',2,NULL,0),(19,3,'查看',NULL,'sys:role:list,sys:role:info',2,NULL,0),(20,3,'新增',NULL,'sys:role:save,sys:menu:list',2,NULL,0),(21,3,'修改',NULL,'sys:role:update,sys:menu:list',2,NULL,0),(22,3,'删除',NULL,'sys:role:delete',2,NULL,0),(23,4,'查看',NULL,'sys:menu:list,sys:menu:info',2,NULL,0),(24,4,'新增',NULL,'sys:menu:save,sys:menu:select',2,NULL,0),(25,4,'修改',NULL,'sys:menu:update,sys:menu:select',2,NULL,0),(26,4,'删除',NULL,'sys:menu:delete',2,NULL,0),(27,1,'参数管理','sys/config','sys:config:list,sys:config:info,sys:config:save,sys:config:update,sys:config:delete',1,'config',6),(29,1,'系统日志','sys/log','sys:log:list',1,'log',7),(30,1,'文件上传','oss/oss','sys:oss:all',1,'oss',6),(31,0,'商品系统','','',0,'editor',0),(32,31,'分类维护','product/category','',1,'menu',0),(34,31,'品牌管理','product/brand','',1,'editor',0),(37,31,'平台属性','','',0,'system',0),(38,37,'属性分组','product/attrgroup','',1,'tubiao',0),(39,37,'规格参数','product/baseattr','',1,'log',0),(40,37,'销售属性','product/saleattr','',1,'zonghe',0),(41,31,'商品维护','product/spu','',0,'zonghe',0),(42,0,'优惠营销','','',0,'mudedi',0),(43,0,'库存系统','','',0,'shouye',0),(44,0,'订单系统','','',0,'config',0),(45,0,'用户系统','','',0,'admin',0),(46,0,'内容管理','','',0,'sousuo',0),(47,42,'优惠券管理','coupon/coupon','',1,'zhedie',0),(48,42,'发放记录','coupon/history','',1,'sql',0),(49,42,'专题活动','coupon/subject','',1,'tixing',0),(50,42,'秒杀活动','coupon/seckill','',1,'daohang',0),(51,42,'积分维护','coupon/bounds','',1,'geren',0),(52,42,'满减折扣','coupon/full','',1,'shoucang',0),(53,43,'仓库维护','ware/wareinfo','',1,'shouye',0),(54,43,'库存工作单','ware/task','',1,'log',0),(55,43,'商品库存','ware/sku','',1,'jiesuo',0),(56,44,'订单查询','order/order','',1,'zhedie',0),(57,44,'退货单处理','order/return','',1,'shanchu',0),(58,44,'等级规则','order/settings','',1,'system',0),(59,44,'支付流水查询','order/payment','',1,'job',0),(60,44,'退款流水查询','order/refund','',1,'mudedi',0),(61,45,'会员列表','member/member','',1,'geren',0),(62,45,'会员等级','member/level','',1,'tubiao',0),(63,45,'积分变化','member/growth','',1,'bianji',0),(64,45,'统计信息','member/statistics','',1,'sql',0),(65,46,'首页推荐','content/index','',1,'shouye',0),(66,46,'分类热门','content/category','',1,'zhedie',0),(67,46,'评论管理','content/comments','',1,'pinglun',0),(68,41,'spu管理','product/spu','',1,'config',0),(69,41,'发布商品','product/spuadd','',1,'bianji',0),(70,43,'采购单维护','','',0,'tubiao',0),(71,70,'采购需求','ware/purchaseitem','',1,'editor',0),(72,70,'采购单','ware/purchase','',1,'menu',0),(73,41,'商品管理','product/manager','',1,'zonghe',0),(74,42,'会员价格','coupon/memberprice','',1,'admin',0),(75,42,'每日秒杀','coupon/seckillsession','',1,'job',0);

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;


2. Front-end component extraction

Now I want to click on the left side of the menu to display data on the right side
insert image description here

(1) The extraction module is stored in common

Create a view/modulesnew commonfolder to store the modules that need to be extracted

extract category.vue, left content

<template>
  <div>
    <el-input placeholder="输入关键字进行过滤" v-model="filterText"></el-input>
    <el-tree
      :data="menus"
      :props="defaultProps"
      node-key="catId"
      ref="menuTree"
      @node-click="nodeclick"
      :filter-node-method="filterNode"
      :highlight-current = "true"
    ></el-tree>
  </div>
</template>

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

export default {
    
    
  //import引入的组件需要注入到对象中才能使用
  components: {
    
    },
  props: {
    
    },
  data() {
    
    
    //这里存放数据
    return {
    
    
      filterText: "",
      menus: [],
      expandedKey: [],
      defaultProps: {
    
    
        children: "children",
        label: "name"
      }
    };
  },
  //计算属性 类似于data概念
  computed: {
    
    },
  //监控data中的数据变化
  watch: {
    
    
    filterText(val) {
    
    
      this.$refs.menuTree.filter(val);
    }
  },
  //方法集合
  methods: {
    
    
    //树节点过滤
    filterNode(value, data) {
    
    
      if (!value) return true;
      return data.name.indexOf(value) !== -1;
    },
    getMenus() {
    
    
      this.$http({
    
    
        url: this.$http.adornUrl("/product/category/list/tree"),
        method: "get"
      }).then(({
    
     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 scoped>

</style>

(2)attrgroup.view

<template>
  <el-row :gutter="20">
    <el-col :span="6">
      <category></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>

import Category from "../common/category";
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: {
    
    
    // 获取数据列表
    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 scoped>
</style>

3. Parent-child components pass data

To realize the function: click on the left side, and the corresponding content will be displayed in the table on the right side.

Parent-child components pass data: category.vuewhen clicked, those who refer attgroup.vueto it can perceive it, and then notifyadd-or-update


(1) The child component passes data to the parent component

  • The child component ( ) passes data categoryto the parent component ( ), using the event mechanismattrgroup
  • The child component sends an event to the parent component and carries the data,this.$emit
  • node-click : the callback when the node is clicked, categorythe middle binding event@node-click="nodeclick"
<el-tree :data="menus" :props="defaultProps" node-key="catId" ref="menuTree" @node-click="nodeclick" :filter-node-method="filterNode" :highlight-current="true"></el-tree>

// 点击节点触发的事件
    nodeclick(data, node, component) {
    
    
      console.log("子组件category的节点被点击", data, node, component);
      //向父组件发送事件;
      this.$emit("tree-node-click", data, node, component);
    }

(2) Get the sent event in the parent component

// 表明他的子组件可能会传递过来点击事件,用自定义的函数接收传递过来的参数
<category  @tree-node-click="treeNodeClick"></category>

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


4. Obtain classification attribute grouping

insert image description here

  1. ReviseAttrGroupController
  /**
     * 列表
     */
    @RequestMapping("/list/{catelogId}")
    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);
    }
  1. AttrGroupServiceImpl
 // 根据分类返回属性分组,按关键字或者按id查
    @Override
    public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
    
    

        String key = (String) params.get("key");
        QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<>();

        // select * from AttrGroup where attr_group_id='key' or attr_group_name like 'key'
        if (!StringUtils.isEmpty(key)) {
    
    
            // 传入consumer
            wrapper.and((obj) ->
                    obj.eq("attr_group_id", key).or().like("attr_group_name", key)
            );
        }

        //  0的话查所有
        if (catelogId == 0) {
    
    
            // Query可以把map封装为IPage
            IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
            return new PageUtils(page);
        } else {
    
    
            wrapper.eq("catelog_id", catelogId);
            IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
            return new PageUtils(page);
        }
    }
  1. front-end processing
    attrgroup.vue

data() {
    
    
    //这里存放数据
    return {
    
    
      catId: 0,
      
      }
   }
   
// 1.获取发送的事件数据,感知树节点被点击
    treeNodeClick(data, node, component) {
    
    
    
      if (node.level == 3) {
    
    
        this.catId = data.catId;
        this.getDataList(); //重新查询
      }
    },

	 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
	        })

5. New grouping

(1) Cascade selector<el-cascader>

  • categoryIds: [] accepts an array of category ids, and submits the last id when submitting

attrgroup-add-or-update.vue

 <el-form-item label="所属分类id" prop="catelogId">
        <!-- <el-input v-model="dataForm.catelogId" placeholder="所属分类id"></el-input> @change="handleChange" -->
        <el-cascader filterable placeholder="试试搜索:手机" v-model="dataForm.catelogIds" :options="categorys" :props="props"></el-cascader>
      </el-form-item>

  data() {
    
    
    return {
    
    
      props: {
    
    
        value: "catId",
        label: "name",
        children: "children"
      },
      categorys: [],
        dataForm: {
    
    
        attrGroupId: 0,
        attrGroupName: '',
        sort: '',
        descript: '',
        icon: '',
        catelogIds: [],
        catelogId: 0
      },


(2) Optimization

When there is no lower-level menu, do not have a lower-level empty menu. On the java side, remove the null value of the children attribute, and
remove the field when the collection is empty.

insert image description here

Can be annotated with @JsonInclude(Inlcude.NON_EMPTY) on the properties of the entity class

@JsonInclude(JsonInclude.Include.NON_EMPTY)
	@TableField(exist = false)
	private List<CategoryEntity> children;

6. Modify the echo

Echo level 3 when modifying, echo in the init method

insert image description here


  1. attrgroup-add-or-update.vueIn front-end modification and optimization, catelogIdsreplace all withcatelogPath
 init(id) {
    
    
      ...
       // 查出catelogId的完整路径
        this.dataForm.catelogPath =  data.attrGroup.catelogPath;
            }
          })
        }
      })
    },
  1. Modify the backend interface
    AttrGroupEntityto add catelogPathfields
@TableField(exist = false)
	private Long[] catelogPath;

AttrGroupController

 @RequestMapping("/info/{attrGroupId}")
    public R info(@PathVariable("attrGroupId") Long attrGroupId) {
    
    
        AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);

        // 1.当前catelogId
        Long catelogId = attrGroup.getCatelogId();

        // 2.根据 当前路径找到完整路径
        Long[] path = categoryService.findCateLogPath(catelogId);

        // 3. 设置完整路径
        attrGroup.setCatelogPath(path);

        return R.ok().put("attrGroup", attrGroup);
    }

CategoryService

  /**
     * 找到catelogId的完整路径
     * 父/子/孙
     * @param catelogId
     * @return
     */
    Long[] findCateLogPath(Long catelogId);

CategoryServiceImpl

 // 找到catelogId的完整路径
    @Override
    public Long[] findCateLogPath(Long catelogId) {
    
    
        List<Long> paths = new ArrayList<>();
        List<Long> parentPath = findParentPath(catelogId, paths);
        // 收集的时候是顺序 前端是逆序显示的 所以用集合工具类给它逆序一下
        Collections.reverse(parentPath);
        return (Long[]) parentPath.toArray();

    }

    //递归收集所有父节点
    private List<Long> findParentPath(Long catlogId, List<Long> paths) {
    
    
        // 1、收集当前节点id
        paths.add(catlogId);
        // 2、查找当前节点的详细信息
        CategoryEntity byId = this.getById(catlogId);
        // 3、有父id
        if (byId.getParentCid() != 0) {
    
    
            // 4、递归查找
            findParentPath(byId.getParentCid(), paths);
        }
        return paths;
    }
  1. Optimized
    to clear the content when the session is closed to prevent the remaining data from being opened next time
  • The listening dialog is closed:@closed="dialogClose"
  • Clear the data when the dialog is closed
 <el-dialog :title="!dataForm.attrGroupId ? '新增' : '修改'" :close-on-click-modal="false" :visible.sync="visible" @closed="dialogClose">

  methods: {
    
    

	// 2. 对话框关闭时清空数据
    dialogClose(){
    
    
      this.dataForm.catelogPath=[];
    },
  • filterableCascading selectors are searchable

Guess you like

Origin blog.csdn.net/ljn1046016768/article/details/124597726