• 谷粒商城九商品服务商品基本概念与属性分组todo


    之前的文章我都是把整篇的代码直接复制到文章中,这样容易抓不住重点,
    但是一段代码都贴出来,又显得繁琐,
    从这篇开始,我会把重点步骤写出来,代码还是贴完整的

    从这篇开始的mybatis-plus分页插件的使用开始,就完全不写前端页面了,老师说之前学过的足够可以看懂了

    这篇文章的作用主要是一些基本概念和理解表的商品表的关联关系,前端的都是贴过来的,后端都是单表的crud,所以代码实在是不值得写了

    商品基本概念

    spu sku

    SPU:Standard Product Unit(标准化产品单元) 是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一 个产品的特性。

    SKU:Stock Keeping Unit(库存量单位) 即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU 这是对于大型连锁超市 DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每 种产品均对应有唯一的 SKU 号。

    iphoneX 是 SPU、MI 8 是 SPU,它们聚合了这款产品所有的特性信息,通过spu可以知道这款产品的像素、内存、cpu等等

    当我们买这款产品的时候就需要说清楚各种配置,也就是我们买到手的其实是sku
    iphoneX 64G 黑曜石 是 SKU MI8 8+64G+黑色 是 SKU

    有点像我们java中的类与对象的关系

    拿京东页面的iphone举例

    这些iphone公用的信息就是spu,

    在这里插入图片描述

    256G、深空灰就是一个sku,

    一个spu下的所有sku相同的属性叫作基本属性,体现在商品页面中就是商品介绍、规格与包装

    一个spu下的所有sku不同的属性叫作销售属性,也就是可以确定库存与价格的,例如颜色、内存
    在这里插入图片描述

    基本属性【规格参数】与销售属性

    下面的举例都是基于上面第一张图片

    每个分类下的商品共享规格参数,与销售属性。只是有些商品不一定要用这个分类下全部的 属性;

    • 属性是以三级分类组织起来的

    基本属性与销售属性都是以三级分类组织起来的,
    例如 手机通讯-手机-手机这个三级分类下的属性字段应该是相似的,例如品牌、产品名称这些字段。

    • 规格参数中有些是可以提供检索的

    例如屏幕尺寸、运行内存、机身存储

    • 规格参数也是基本属性,他们具有自己的分组

    例如入网型号、品牌、产品名称、上市年份都属于规格与包装的主体

    • 属性的分组也是以三级分类组织起来的

    和上面的属性是以三级分类组织起来的一个意思

    • 属性名确定的,但是值是每一个商品不同来决定的

    数据库设计

    由此关联关系我们可以得出该三级分类下有什么属性
    在这里插入图片描述

    在这里插入图片描述

    # 品牌与分类是多对多的关系
    # pms_category_brand_relation品牌分类关联表
    
    # 本身这个表有品牌id和分类id就足够了,但是我们经常在查两表关联关系的时候都需要其name,所以这两个name就成了冗余字段
    
    CREATE TABLE `gulimall_pms`.`Untitled`  (
      `id` bigint(20) NOT NULL AUTO_INCREMENT,
      `brand_id` bigint(20) NULL DEFAULT NULL COMMENT '品牌id',
      `catelog_id` bigint(20) NULL DEFAULT NULL COMMENT '分类id',
      `brand_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
      `catelog_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
      PRIMARY KEY (`id`) USING BTREE
    ) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '品牌分类关联' ROW_FORMAT = Dynamic;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    数据

    • 三级分类:手机通讯-手机-手机
    • spu:iphone12
    • sku:黑色 64G
    • 属性分组:主体
    • 属性:入网型号、品牌、产品名称、上市年份都属于规格与包装的

    根据以上数据来做数据库的表设计

    • pms_attr属性表:属性名(例如入网型号)、属性类型、三级分类id、是否可被检索

    • pms_attr_group属性分组表:组名(例如主体)、三级分类

    • pms_attr_attrgroup_relation属性分组和属性关联表:属性id、属性分组id

    • pms_product_attr_value商品属性值表:spuid、属性id、属性值

    • pms_spu_info spu表:spu基本信息、三级分类

    • pms_sku_info sku表:sku基本信息、与spu的关系是多对一

    • pms_sku_images sku图片表

    • pms_sku_sale_attr_value sku销售属性表:skuid、sku属性和值

    总结商品各表逻辑

    商品品牌、属性分组、规格参数(基本属性)、属性分组(基本属性分组)、销售属性,都是基于三级分类

    1. 商品品牌与三级分类是多对多
    2. 基本属性(规格参数)和销售属性两表与三级分类都是多对多todo这个不太确定,等确定后补充吧
    3. 基本属性和基本属性分组应属同一三级分类,且不同基本属性分组不能拥有同样的基本属性

    注意

    1. 考虑到数据量大的情况,我们不做连表查询,例如一张表有100W的数据,另一张表只有1000数据,如果在极端情况下,他们做笛卡尔积,就会有10亿的数据,这是非常可怕的。
    2. 更新多对多两表的同时需要更新他们的中间表
    3. 规格参数(基本属性),只有基本属性是分组的,销售属性是不设置属性分组的,但他俩公用一张表

    后端使用mybatis-plus分页插件

    <dependency>
                <groupId>com.baomidougroupId>
                <artifactId>mybatis-plus-boot-starterartifactId>
                <version>3.4.0version>
    
    
            dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    package com.atlinxi.gulimall.product.config;
    
    import com.baomidou.mybatisplus.annotation.DbType;
    import com.baomidou.mybatisplus.autoconfigure.ConfigurationCustomizer;
    import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
    import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
    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.atlinxi.gulimall.product.dao")
    public class MybatisConfig {
    
        // 引入mybatis-plus分页插件
        /**
         * 新的分页插件,一缓和二缓遵循mybatis的规则,需要设置 MybatisConfiguration#useDeprecatedExecutor = false 避免缓存出现问题(该属性会在旧插件移除后一同移除)
         */
        @Bean
        public MybatisPlusInterceptor mybatisPlusInterceptor() {
            MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
            interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
            return interceptor;
        }
    
        @Bean
        public ConfigurationCustomizer configurationCustomizer() {
            return configuration -> configuration.setUseDeprecatedExecutor(false);
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    Object划分

    1. PO(persistant object) 持久对象 PO 就是对应数据库中某个表中的一条记录,多个记录可以用 PO 的集合。 PO 中应该不包 含任何对数据库的操作。 例如entity

    2. DO(Domain Object)领域对象 就是从现实世界中抽象出来的有形或无形的业务实体。

    3. TO(Transfer Object) ,数据传输对象 不同的应用程序之间传输的对象,例如微服务之间传递数据封装的对象

    4. DTO(Data Transfer Object)数据传输对象 这个概念来源于 J2EE 的设计模式,原来的目的是为了 EJB 的分布式应用提供粗粒度的 数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这 里,泛指用于展示层与服务层之间的数据传输对象。

    5. VO(value object) 值对象 通常用于业务层之间的数据传递,和 PO 一样也是仅仅包含数据而已。但应是抽象出 的业务对象 , 可以和表对应 , 也可以不 , 这根据业务的需要 。用 new 关键字创建,由 GC 回收的。

      View object:视图对象; 接受页面传递来的数据,封装对象 将业务处理完成的对象,封装成页面要用的数据

      例如我们的校验注解都标注在entity上,是非常不规范的

    6. BO(business object) 业务对象 从业务模型的角度看 , 见 UML 元件领域模型中的领域对象。封装业务逻辑的 java 对 象 , 通过调用 DAO 方法 , 结合 PO,VO 进行业务操作。business object: 业务对象 主要作 用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。 比如一个简 历,有教育经历、工作经历、社会关系等等。 我们可以把教育经历对应一个 PO ,工作经 历对应一个 PO ,社会关系对应一个 PO 。 建立一个对应简历的 BO 对象处理简历,每 个 BO 包含这些 PO 。 这样处理业务逻辑时,我们就可以针对 BO 去处理。

    7. POJO(plain ordinary java object) 简单无规则 java 对象 传统意义的 java 对象。就是说在一些 Object/Relation Mapping 工具中,能够做到维护 数据库表记录的 persisent object 完全是一个符合 Java Bean 规范的纯 Java 对象,没有增 加别的属性和方法。我的理解就是最基本的 java Bean ,只有属性字段及 setter 和 getter 方法!。POJO 是 DO/DTO/BO/VO 的统称。

    8. DAO(data access object) 数据访问对象 是一个 sun 的一个标准 j2ee 设计模式, 这个模式中有个接口就是 DAO ,它负持久 层的操作。为业务层提供接口。此对象用于访问数据库。通常和 PO 结合使用, DAO 中包 含了各种数据库的操作方法。通过它的方法 , 结合 PO 对数据库进行相关的操作。夹在业 务逻辑与数据库资源中间。配合 VO, 提供数据库的 CRUD 操作.

    常用的

    1. 各业务模块创建vo包,用来封装和前端传递数据的对象

    2. to,微服务之间传递数据封装的对象

    a服务调用b服务,在数据传输的过程中,a服务的数据以对象的形式传出去,springcloud将对象转换成json,b服务在接收数据的时候又把json转换成对象,所以两个服务都需要使用这个实体类,我们把它定义在common模块的to包中

    然而我们在使用逆向工程创建好的代码的时候,保存的通常是实体类的entity,那么我们调用方使用to,被调用方使用使用entity也是可以的,因为它数据转换的最终原理是对象和json的转换,只要双方数据结构及其属性是一致的即可

    属性分组菜单实现

    选中一个三级分类,查询该分类下已经有的分组,
    在这里插入图片描述

    renren-fast-vue创建所有菜单页面

    之前我们已经创建过两次分类了,这次就不手动执行了,是一个sys_menus.sqlsql文件,给sys_menu执行,
    在这里插入图片描述

    api在线文档

    todo有个文档技术是swagger,有时间自己能不能把它实现以下,把自己写过的这些接口弄一个文档出来

    前端抽取三级分类组件

    属性分组、规格参数、销售属性菜单栏都需要三级分类,所以我们可以将三级分类功能抽取出来。

    属性分组的菜单url是http://localhost:8001/#/product-attrgroup,所以我们在prduct目录下创建attrgroup.vue

    src\views\modules创建common\category.vue,用来抽取三级分类的组件

    <template>
      <div>
        <el-tree
          :data="menus"
          :props="defaultProps"
          node-key="catId"
          ref="menuTree"
        >
          <!-- 每一个分类都会跟一个这个span 也就是 Append Delete -->
          <!-- node为当前节点,例如有没有展开,有没有选中之类
                data是这个节点真正的数据 -->
          <span class="custom-tree-node" slot-scope="{ node, data }">
            <span>{{ node.label }}</span>
          </span>
        </el-tree>
      </div>
    </template>
    
    <script>
    //这里可以导入其他文件(比如:组件,工具 js,第三方插件 js,json 文件,图片文件等等)
    //例如:import 《组件名称》 from '《组件路径》';
    
    export default {
      //import 引入的组件需要注入到对象中才能使用
      components: {},
      props: {},
      data() {
        //这里存放数据
        return {
          menus: [],
          defaultProps: {
            // 以哪个字段展开分类
            children: "children",
            // 哪个字段为分类名称
            label: "name",
          },
          expandedKey: [],
        };
      },
      //计算属性 类似于 data 概念
      computed: {},
      //监控 data 中的数据变化
      watch: {},
      //方法集合
      methods: {
        getMenus() {
          this.$http({
            url: this.$http.adornUrl("/product/category/list/tree"),
            method: "get",
          }).then(({ data }) => {
            this.menus = data.data;
          });
        },
      },
    
      //生命周期 - 创建完成(可以访问当前 this 实例)
      created() {
        this.getMenus();
      },
    
      //生命周期 - 挂载完成(可以访问 DOM 元素)
      mounted() {},
      beforeCreate() {}, //生命周期 - 创建之前
      beforeMount() {}, //生命周期 - 挂载之前
      beforeUpdate() {}, //生命周期 - 更新之前
      updated() {}, //生命周期 - 更新之后
      beforeDestroy() {}, //生命周期 - 销毁之前
      destroyed() {}, //生命周期 - 销毁完成
      activated() {}, //如果页面有 keep-alive 缓存功能,这个函数会触发
    };
    </script>
    <style scoped>
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73

    将我们抽取出来的category.vue导入到attrgroup.vue

    将逆向生成的attrgroup.vue粘贴到,太简单了,下面代码就不体现了

    将逆向生成的attrgroup-add-or-update.vue复制到项目中

    attrgroup.vue

    <template>
      <!--  gutter 列间距 -->
      <!-- el-row 代表行 最多可以有24-->
      <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>
    //这里可以导入其他文件(比如:组件,工具 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,
        };
      },
      //计算属性 类似于 data 概念
      computed: {},
      //监控 data 中的数据变化
      watch: {},
      //方法集合
      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);
              }
            });
          });
        },
      },
    
      //生命周期 - 创建完成(可以访问当前 this 实例)
      created() {},
    
      //生命周期 - 挂载完成(可以访问 DOM 元素)
      mounted() {},
      beforeCreate() {}, //生命周期 - 创建之前
      beforeMount() {}, //生命周期 - 挂载之前
      beforeUpdate() {}, //生命周期 - 更新之前
      updated() {}, //生命周期 - 更新之后
      beforeDestroy() {}, //生命周期 - 销毁之前
      destroyed() {}, //生命周期 - 销毁完成
      activated() {
        this.getDataList();
      }, //如果页面有 keep-alive 缓存功能,这个函数会触发
    };
    </script>
    <style scoped>
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268

    在这里插入图片描述

    根据三级分类查询属性分组

    此时我们的需求是attrgroup.vue的页面是随着category.vue中的三级分类变化而变化

    查询

    使用到的vue技术,vue高级功能 父子组件传递数据

    1. 给category.vue绑定点击事件,获取该分类的信息
    2. 向父组件发送事件
    3. 父组件感知子组件的树节点被点击
    4. 编写后端根据category_id查询属性分组
    5. 前端父组件感知子组件的树节点被点击的时候,获取属性列表

    新增与更新

    1. 使用el-cascader标签完成级联选择器
    2. 级联选择器将空children也渲染,在entity的children上加注解实现为空即不响应
    3. 当新增分类,子组件向父组件发送刷新页面的指令
    4. 更新的回显逆向工程已经生成了,但是分类id我们在新增的时候是存储的三级分类的id,回显的时候是需要其一级分类和二级分类的
    5. 后端递归查找三级分类的完整路径
    6. 前端添加更新时三级分类可搜索功能

    完整code

    attrgroup.vue

    <template>
      <!--  gutter 列间距 -->
      <!-- el-row 代表行 最多可以有24-->
      <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>
    /**
     * vue高级功能 父子组件传递数据
     * 
     *    父组件就是本vue,子组件就是我们引入的category.vue
     * 
     *    我们需要category一点,父组件可以感知到
     * 
     * 
     *    1)子组件给父组件传递数据,事件机制
     *          
     *          子组件给父组件发送一个事件,事件携带数据
     * 
     *              这种操作类似于冒泡,小div点击的同时大div也可以感知到
     * 
     *       // 事件名(随便写)、接下来的参数就是携带的数据,可以写无数个
                this.$emit('tree-node-click',data,node,component) 
     * 
     * 
     */
    //这里可以导入其他文件(比如:组件,工具 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: "",
          },
          catId: 0,
          dataList: [],
          pageIndex: 1,
          pageSize: 10,
          totalPage: 0,
          dataListLoading: false,
          dataListSelections: [],
          addOrUpdateVisible: false,
        };
      },
      //计算属性 类似于 data 概念
      computed: {},
      //监控 data 中的数据变化
      watch: {},
      //方法集合
      methods: {
        // 感知子组件的树节点被点击
        treeNodeClick(data, node, component) {
          if (node.level == 3) {
            console.log("父组件感知点击", data, data.catId);
            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;
          });
        },
        // 每页数
        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.$nextTick(() => {
            // this.$refs 当前vue下的所有组件
            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);
              }
            });
          });
        },
      },
    
      //生命周期 - 创建完成(可以访问当前 this 实例)
      created() {},
    
      //生命周期 - 挂载完成(可以访问 DOM 元素)
      mounted() {},
      beforeCreate() {}, //生命周期 - 创建之前
      beforeMount() {}, //生命周期 - 挂载之前
      beforeUpdate() {}, //生命周期 - 更新之前
      updated() {}, //生命周期 - 更新之后
      beforeDestroy() {}, //生命周期 - 销毁之前
      destroyed() {}, //生命周期 - 销毁完成
      activated() {
        this.getDataList();
      }, //如果页面有 keep-alive 缓存功能,这个函数会触发
    };
    </script>
    <style scoped>
    </style>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299

    attrgroup-add-or-update.vue

    <template>
      <el-dialog
        :title="!dataForm.attrGroupId ? '新增' : '修改'"
        :close-on-click-modal="false"
        :visible.sync="visible"
        @closed="dialogClose"
      >
        <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> -->
            <!-- @change="handleChange" -->
            <!--  -->
            <el-cascader
              :props="props"
              v-model="dataForm.catelogIds"
              :options="categorys"
              filterable
              placeholder="试试搜索:手机"
            ></el-cascader>
          </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 {
          props: { label: "name", children: "children", value: "catId" },
          categorys: [],
          visible: false,
          dataForm: {
            attrGroupId: 0,
            attrGroupName: "",
            sort: "",
            descript: "",
            icon: "",
            catelogIds: [],
            catelogId: 0,
          },
          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: {
        dialogClose(){
          this.dataForm.catelogIds = []
          console.log()
        },
        getCategorys() {
          this.$http({
            url: this.$http.adornUrl("/product/category/list/tree"),
            method: "get",
          }).then(({ data }) => {
            this.categorys = data.data;
          });
        },
    
        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.catelogIds = data.attrGroup.catelogPath;
                }
              });
            }
          });
        },
    
        // 表单提交
        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.catelogIds[this.dataForm.catelogIds.length - 1],
                }),
              }).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);
                }
              });
            }
          });
        },
      },
      created() {
        this.getCategorys();
      },
    };
    </script>
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167

    CategoryServiceImpl

    /**
         * 根据三级分类查找其路径
         * @return
         */
        @Override
        public Long[] findCatelogPath(Long catelogId) {
    
            List<Long> paths = new ArrayList<>();
    
            List<Long> parentPath = findParentPath(catelogId, paths);
    
            Collections.reverse(parentPath);
    
    
            return paths.toArray(new Long[parentPath.size()]);
        }
    
    
        private List<Long> findParentPath(Long catelogId,List<Long> paths){
    
            paths.add(catelogId);
            // mabatis-plus return this.getBaseMapper().selectById(id);
            CategoryEntity byId = this.getById(catelogId);
    
            if (byId.getParentCid()!=0){
                findParentPath(byId.getParentCid(),paths);
            }
    
            return paths;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    新增商品

    新增商品四个步骤

    1. 基本信息

    商品名称、描述、分类、品牌、重量(用来进行邮费的计算)、积分(送金币(抵消商品金额)和成长值(提高用户等级))、商品介绍图、商品图集(所有sku的图片)

    1. 规格参数(基本属性)

    前两步录入的实际都是spu的信息

    1. 销售属性

    可以通过组合销售属性确定一个sku

    1. 设置sku

    组合销售属性确定一个sku,例如 白色+64G+8g

    标题、副标题,随着sku的变化而变化

    价格、满减信息、折扣信息、默认显示图片

    获取该分类下的品牌

    小数和金额以后都用BigDecimal,运算就不会丢失精度,我们数据库所有字段都是Long

    调用远程服务 coupon 保存优惠满减信息

    common创建to包

    这块儿的东西都是后端的,前端都是贴过来的,而且后端都是单表的crud,而且逻辑不复杂,所以实在没什么可记录的

    仓储服务

    采购单

    我们商品库存菜单下的商品不应该是在这个界面直接新增修改的,测试期间我们暂时保留这两个功能。

    我们所有的库存都应该是由采购完成之后自动加上的。

    采购简要流程

    1. 人工在后台建立了一个采购需求
    2. 系统自动发出了低库存预警并创建了采购需求

    在这里插入图片描述

    分布式基础篇总结

    在这里插入图片描述

    她们同时想道:秋天迟到了,天气还那么热,才吹电风扇,为什么伊纹姐姐要穿高领长袖?

    房思琪的初恋乐园
    林奕含

  • 相关阅读:
    Python大数据之linux学习总结——day10_hive调优
    SpringCloud 常见问题
    【Spark】Spark 高频面试题英语版(1)
    Cesium 展示——移除已加载的geojson文件效果
    NLP时政有害信息的界定
    SASS/SCSS精华干货教程
    MVVM理解+Object.defineproperty方法+数据代理
    【数据结构】B树、B+树的知识点学习总结
    最短路径Dijkstra算法详解
    nRF Connect SDK(NCS)/Zephyr固件升级详解 – 重点讲述MCUboot和蓝牙空中升级
  • 原文地址:https://blog.csdn.net/weixin_44431371/article/details/127567527