java电商项目(二)

本文涉及的产品
实时计算 Flink 版,5000CU*H 3个月
检索分析服务 Elasticsearch 版,2核4GB开发者规格 1个月
实时数仓Hologres,5000CU*H 100GB 3个月
简介: 本文档详细介绍了商品分类和规格参数的实现过程。商品分类分为三级管理,主要用于首页商品导航和后台商品管理,采用树状结构存储。规格参数则用于描述商品的具体属性,包括SPU和SKU的定义,规格参数与分类绑定,支持搜索过滤。文档涵盖了表结构设计、实体类、持久层、业务层、控制层的实现,并提供了前端组件的示例代码,确保前后端无缝对接。

[TOC]

1 商品分类

1.1 需求分析

商品分类一共分三级管理,主要作用是在网站首页中显示商品导航,以及在管理后台管理商品时使用。

前端交互方式见管理后台->商品管理->类型管理

1.2 表结构分析

category_ 表 (商品分类

字段名称 字段含义 字段类型 字段长度 备注
id_ 分类ID INT
title_ 分类名称 VARCHAR
order_ 排序 INT
parentid 上级ID INT
isparent 是否为根节点 boolean
expand_ 是否默认展开 boolean

商品分类与模板是多对一关系

1.3 实现

1.3.1 实体类

legou-item/legou-item-instance/src/main/java/com/lxs/legou/item/po/Category.java:

package com.lxs.legou.item.po;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.lxs.legou.core.po.BaseTreeEntity;
import lombok.Data;

/**
 * @Title: 商品分类
 */
@Data
@TableName("category_")
public class Category extends BaseTreeEntity {
   

    @TableField("is_parent_")
    private Boolean isParent = false; //是否为父节点

    @TableField(exist = false)
    private Integer isRoot = 0; //值=1 : 查询根节点条件

    public String getLabel() {
    //treeselect需要的属性
        return this.getTitle();
    }

}

注意因为Category分类是树状结构,所以这里需要继承BaseTreeEntity

1.3.2 持久层

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/dao/CategoryDao.java:

package com.lxs.legou.item.dao;

import com.lxs.legou.core.dao.ICrudDao;
import com.lxs.legou.item.po.Category;

/**
 * @file 分类Dao
 */
public interface CategoryDao extends ICrudDao<Category> {
   

}

这里不需要动态SQL语句查询,所以此处不用谢映射文件

1.3.3 业务层

(1)业务层接口

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/ICategoryService.java:

package com.lxs.legou.item.service;

import com.lxs.legou.core.service.ICrudService;
import com.lxs.legou.item.po.Category;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @Title:
 */
@Service
public interface ICategoryService extends ICrudService<Category> {
   

}

(2)业务层接口实现类

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/impl/CategoryServiceImpl.java:

package com.lxs.legou.item.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lxs.legou.core.service.impl.CrudServiceImpl;
import com.lxs.legou.item.po.Category;
import com.lxs.legou.item.service.ICategoryService;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @Title:
 */
@Service
public class CategoryServiceImpl extends CrudServiceImpl<Category> implements ICategoryService {
   

    @Override
    public List<Category> list(Category entity) {
   
        QueryWrapper<Category> queryWrapper = Wrappers.query();
        if (StringUtils.isNotEmpty(entity.getTitle())) {
   
            queryWrapper.like("title_", entity.getTitle());
        }
        if (null != entity.getParentId()) {
   
            queryWrapper.eq("parent_id_", entity.getParentId());
        }
        if (null != entity.getIsRoot() && entity.getIsRoot().equals(1)) {
   
            queryWrapper.isNull("parent_id_");
        }

        return getBaseMapper().selectList(queryWrapper);
    }

}

1.3.4 控制层

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/controller/CategoryController.java:

package com.lxs.legou.item.controller;

import com.lxs.legou.core.controller.BaseController;
import com.lxs.legou.item.po.Category;
import com.lxs.legou.item.service.ICategoryService;
import io.swagger.annotations.ApiOperation;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Title: 分类控制器
 */
@RestController
@RequestMapping("/category")
public class CategoryController extends BaseController<ICategoryService, Category> {
   


}

1.3.5 测试

1.3.6 前端组件

1.3.6.1 树状组件基类

src/libs/crud/base-tree.js:

base-tree组件继承base-list完成树状数据的列表页的基本方法和逻辑的重用

import Qs from 'qs'
import { listToTree } from '@/libs/util'
import instance from '@/libs/api/index'


import {baseList} from './base-list'

export const baseTree = {
  mixins: [baseList],
  // 表格各行的数据
  data () {
    return {
      // 初始化信息总条数
      total: 0,
      // 每页显示多少条
      pageSize: 10,
      // 显示的数据
      rows: []
    }
  },
  methods: {
    renderContent (h, { root, node, data }) {
      return h('span', {
        style: {
          display: 'inline-block',
          width: '100%'
        }
      }, [
        h('span', [
          h('Icon', {
            props: {
              type: 'ios-paper-outline'
            },
            style: {
              marginRight: '8px'
            }
          }),
          h('span', data.title)
        ]),
        h('span', {
          style: {
            display: 'inline-block',
            float: 'right',
            marginRight: '32px'
          }
        }, [
          h('Button', {
            props: Object.assign({}, this.buttonProps, {
              icon: 'md-add'
            }),
            style: {
              marginRight: '8px'
            },
            on: {
              click: () => { this.addChild(data.id) }
            }
          }),
          h('Button', {
            props: Object.assign({}, this.buttonProps, {
              icon: 'md-copy'
            }),
            style: {
              marginRight: '8px'
            },
            on: {
              click: () => { this.edit(data.id) }
            }
          }),
          h('Button', {
            props: Object.assign({}, this.buttonProps, {
              icon: 'md-remove'
            }),
            on: {
              click: () => { this.remove(data.id) }
            }
          })
        ])
      ]);
    },
    // 查询
    query () {
      instance.post(`/${this.namespace}/${this.entityName}/list`, Qs.stringify(this.formData)).then(response => {
        this.rows = listToTree(response.data)
      }).catch(error => {
        console.log(error)
      })
    },
    addRoot () {
      this.$router.push({
        name: `edit_${this.namespace}_${this.entityName}`
      })
    },
    // 添加
    addChild (id) {
      this.$router.push({
        name: `edit_${this.namespace}_${this.entityName}`,
        query: {parentId: id}
      })
    }
  }
}

src/libs/crud/base-tree-edit.js

继承base-edit组件,实现树状节点的修改,添加页面的基本方法的重用

import {baseEdit} from './base-edit'

export const baseTreeEdit = {
  mixins: [baseEdit],
  created () {
    let arrays = this.$route.name.split('-')
    this.namespace = arrays[1]
    this.entityName = arrays[2]

    let id = this.$route.query.id
    this.formData.parentId = this.$route.query.parentId
    if (id) {
      this.get(id)
    }
  }
}
1.3.6.2 分类列表组件

src/view/item/category/list.vue

继承base-tree实现,树状结构数据的增删改查操作

<template>
    <div>
        <div>
            <Form ref="formData" :model="formData" :label-width="80">
                <Row style="margin-top: 10px;">
                    <Col span="16">
                    <FormItem label="名称" prop="title">
                        <Input v-model="formData.title" placeholder="名称"></Input>
                    </FormItem>
                    </Col>
                    <Col span="8">
                        <Divider type="vertical" />
                        <Button type="primary" @click="addRoot">添加</Button>
                        <Button type="primary" @click="query" style="margin-left: 8px">查询</Button>
                    </Col>
                </Row>
            </Form>
        </div>
        <div>
            <Tree :data="rows" :render="renderContent"></Tree>
        </div>
    </div>
</template>

<style scoped>
    .paging {
        float: right;
        margin-top: 10px;
    }
</style>

<script>
import {baseTree} from '@/libs/crud/base-tree'

export default {
  mixins: [baseTree],
  // 表格各行的数据
  data () {
    return {
      formData: {
        title: ''
      }
    }
  }
}
</script>
1.3.6.3:添加修改组件

src/view/item/category/edit.vue

继承base-tree-edit组件,实现分类节点的添加,修改操作

<template>

    <Form ref="form" :model="formData" :rules="ruleValidate" :label-width="80">
        <input type="hidden" v-model="formData.id"/>
        <input type="hidden" v-model="formData.parentId"/>

        <FormItem label="名称" prop="title">
            <Input v-model="formData.title"></Input>
        </FormItem>
        <Row>
            <Col span="12">
                <FormItem label="排序" prop="order">
                    <Input v-model="formData.order"></Input>
                </FormItem>
            </Col>
            <Col span="12">
            <FormItem label="是否展开" prop="expand">
                <Checkbox v-model="formData.expand"></Checkbox>
            </FormItem>
            </Col>
        </Row>

        <FormItem>
            <Button type="primary" @click="handleSubmit('form')">保存</Button>
            <Button type="primary" @click="go2list()" style="margin-left: 8px">关闭</Button>
        </FormItem>

    </Form>

</template>

<script>

import {baseTreeEdit} from '@/libs/crud/base-tree-edit'

export default {
  mixins: [baseTreeEdit],
  data () {
    return {
      formData: {
        id: '',
        parentId: '',
        title: '',
        order: '',
        expand: false
      },
      ruleValidate: {
        title: [
          {required: true, message: '名称不能为空', trigger: 'blur'}
        ]
      }
    }
  }
}
</script>

2 规格参数

2.1 需求分析

2.1.1 SPU和SKU

乐购商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU,了解一下:

  • SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集

  • SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品

以图为例来看:

1526085541996

  • 本页的 华为Mate10 就是一个商品集(SPU)
  • 因为颜色、内存等不同,而细分出不同的Mate10,如亮黑色128G版。(SKU)

可以看出:

  • SPU是一个抽象的商品集概念,为了方便后台的管理。
  • SKU才是具体要销售的商品,每一个SKU的价格、库存可能会不一样,用户购买的是SKU而不是SPU

2.1.2 分析规格参数

仔细查看每一种商品的规格你会发现:

虽然商品规格千变万化,但是同一类商品(如手机)的规格是统一的,有图为证:

华为的规格:

1526087063700

三星的规格:

1526087142454

也就是说,商品的规格参数应该是与分类绑定的。每一个分类都有统一的规格参数模板,但不同商品其参数值可能不同

如下图所示:

1526088168565

2.1.3 SKU的特有规格

SPU中会有一些特殊属性,用来区分不同的SKU,我们称为SKU特有规格。如华为META10的颜色、内存属性。

不同种类的商品,一个手机,一个衣服,其SKU属性不相同。

同一种类的商品,比如都是衣服,SKU属性基本是一样的,都是颜色、尺码等。

这样说起来,似乎SKU的特有属性也是与分类相关的?事实上,仔细观察你会发现,SKU的特有规格是商品规格参数的一部分

1526088981953

也就是说,我们没必要单独对SKU的特有属性进行设计,它可以看做是规格参数中的一部分。这样规格参数中的属性可以标记成两部分:

  • 所有sku共享的规格属性(称为通用规格)
  • 每个sku不同的规格属性(称为特有规格)

1526089506566

2.1.4 搜索属性

打开一个搜索页,我们来看看过滤的条件:

1526090072535

你会发现,过滤条件中的屏幕尺寸、运行内存、网路、机身内存、电池容量、CPU核数等,在规格参数中都能找到:

1526090228171

也就是说,规格参数中的数据,将来会有一部分作为搜索条件来使用。我们可以在设计时,将这部分属性标记出来,将来做搜索的时候,作为过滤条件。要注意的是,无论是SPU的全局属性,还是SKU的特有属性,都有可能作为搜索过滤条件的,并不冲突,而是有一个交集:

1526091216124

后台规格管理

前端门户

2.1.5 功能展示

前端交互方式见管理后台-> 商品管理-> 规格参数

2.2 表结构分析

specgroup 表(规格参数分组)

字段名称 字段含义 字段类型 字段长度 备注
id_ ID INT
cid_ 外键(分类ID) INT
name_ 规格参数分组名称 VARCHAR

specparam 表( 规格具体项)

字段名称 字段含义 字段类型 字段长度 备注
id_ ID INT
cid_ 外键(分类ID) INT
groupid 外键(分组ID) INT
name_ 规格项名称
numeric_ 是否为数字
unit_ 单位
generic_ 是否为sku参数 VARCHAR false:为sku参数
searching_ 是否为搜索项 VARCHAR 搜索范围选择
sements_ 排序 INT

通过上面需求分析得出商品分类、规格分组、规格参数的关系分别是一对多

2.3 规格参数管理

2.3.1 实体类

legou-item/legou-item-instance/src/main/java/com/lxs/legou/item/po/SpecGroup.java:

package com.lxs.legou.item.po;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.lxs.legou.core.po.BaseEntity;
import lombok.Data;

import java.util.List;

@Data
@TableName("spec_group_")
@JsonIgnoreProperties(value = {
   "handler"})
public class SpecGroup extends BaseEntity {
   

    @TableField("cid_")
    private Long cid;

    @TableField("name_")
    private String name;

    @TableField(exist = false)
    private List<SpecParam> params; // 该组下的所有规格参数集合

}

legou-item/legou-item-instance/src/main/java/com/lxs/legou/item/po/SpecParam.java:

package com.lxs.legou.item.po;

import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import com.lxs.legou.core.po.BaseEntity;
import lombok.Data;

@Data
@TableName("spec_param_")
public class SpecParam extends BaseEntity {

    @TableField("cid_")
    private Long cid;
    @TableField("group_id_")
    private Long groupId;
    @TableField("name_")
    private String name;
    @TableField("numeric_")
    private Boolean numeric;
    @TableField("unit_")
    private String unit;
    @TableField("generic_")
    private Boolean generic;
    @TableField("searching_")
    private Boolean searching;
    @TableField("segments_")
    private String segments;


}

2.3.2 持久层

DAO

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/dao/SpecGroupDao.java:

package com.lxs.legou.item.dao;

import com.lxs.legou.core.dao.ICrudDao;
import com.lxs.legou.item.po.SpecGroup;

import java.util.List;

public interface SpecGroupDao extends ICrudDao<SpecGroup> {
   

    /**
     * 根据实体条件动态查询分组
     * @param specGroup
     * @return
     */
    public List<SpecGroup> selectList(SpecGroup specGroup);

}

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/dao/SpecParamDao.java:

package com.lxs.legou.item.dao;

import com.lxs.legou.core.dao.ICrudDao;
import com.lxs.legou.item.po.SpecParam;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface SpecParamDao extends ICrudDao<SpecParam> {

    @Select("select * from spec_param_ where group_id_ = #{groupId}")
    public List<SpecParam> findByGroupId(Integer groupId);

}

映射文件

legou-item/legou-item-service/src/main/resources/mybatis/item/SpecGroupDao.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lxs.legou.item.dao.SpecGroupDao">

   <sql id="selectSql">
        SELECT
            *
        FROM
            spec_group_
    </sql>


   <select id="selectList" resultMap="groupMap">
      <include refid="selectSql"></include>
      <where>
         <if test="cid != null">
            cid_ = #{cid}
         </if>
         <if test="name !=null and name.trim() != ''">
            and rname like '%${name}%'
         </if>
      </where>
   </select>

   <resultMap id="groupMap" type="specGroup">
      <id column="id_" property="id"></id>
      <result property="cid_" column="cid"></result>
      <result property="name_" column="name"></result>
      <collection property="params" javaType="java.util.List" ofType="specParam" select="com.lxs.legou.item.dao.SpecParamDao.findByGroupId" column="id_">
         <id column="id_" property="id"></id>
         <result column="cid_" property="cid"></result>
         <result column="group_id_" property="groupId"></result>
         <result column="name_" property="name"></result>
         <result column="numeric_" property="numeric"></result>
         <result column="unit_" property="unit"></result>
         <result column="generic_" property="generic"></result>
         <result column="searching_" property="searching"></result>
      </collection>
   </resultMap>

   <select id="selectById" resultMap="groupMap">
      <include refid="selectSql"></include>
      where
         id_ = #{id}
   </select>


</mapper>

2.3.3 业务层

(1)业务层接口

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/ISpecGroupService.java:

package com.lxs.legou.item.service;

import com.lxs.legou.core.service.ICrudService;
import com.lxs.legou.item.po.SpecGroup;

import java.util.List;

/**
 * @Title: 商品规格参数分组
 */
public interface ISpecGroupService extends ICrudService<SpecGroup> {
   

    /**
     * 根据前台传递的规格参数列表,保存规格参数
     * @param cid
     * @param groups
     */
    public void saveGroup(Long cid, List<SpecGroup> groups);

}

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/ISpecParamService.java

package com.lxs.legou.item.service;

import com.lxs.legou.core.service.ICrudService;
import com.lxs.legou.item.po.SpecParam;

/**
 * @Title: 商品规格参数
 * @Description:
 *
 * @Copyright 2019 lxs - Powered By 雪松
 * @Author: lxs
 * @Date:  2019/10/9
 * @Version V1.0
 */
public interface ISpecParamService extends ICrudService<SpecParam> {

}

(2)业务层实现类

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/impl/SpecGroupServiceImpl.java:

package com.lxs.legou.item.service.impl;

import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lxs.legou.core.service.impl.CrudServiceImpl;
import com.lxs.legou.item.dao.SpecGroupDao;
import com.lxs.legou.item.dao.SpecParamDao;
import com.lxs.legou.item.po.SpecGroup;
import com.lxs.legou.item.po.SpecParam;
import com.lxs.legou.item.service.ISpecGroupService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
public class SpecGroupServiceImpl extends CrudServiceImpl<SpecGroup> implements ISpecGroupService {
   

    @Autowired
    private SpecParamDao specParamDao;

    @Override
    public List<SpecGroup> list(SpecGroup entity) {
   
        return ((SpecGroupDao) getBaseMapper()).selectList(entity);
    }

    @Override
    @Transactional
    public void saveGroup(Long cid, List<SpecGroup> groups) {
   
        //根据cid删除所有规格参数的分组和规格参数
        getBaseMapper().delete(Wrappers.<SpecGroup>query().eq("cid_", cid));
        specParamDao.delete(Wrappers.<SpecParam>query().eq("cid_", cid));

        //逐个添加规格参数分组和规格参数
        for (SpecGroup group : groups) {
   
            getBaseMapper().insert(group);
            for (SpecParam specParam : group.getParams()) {
   
                specParam.setGroupId(group.getId()); //页面groupId是防止重复自动+1,保存时要保存数据库中groupId
                specParamDao.insert(specParam);
            }
        }
    }
}

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/service/impl/SpecParamServiceImpl.java:

package com.lxs.legou.item.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.lxs.legou.core.service.impl.CrudServiceImpl;
import com.lxs.legou.item.po.SpecParam;
import com.lxs.legou.item.service.ISpecParamService;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class SpecParamServiceImpl extends CrudServiceImpl<SpecParam> implements ISpecParamService {

    @Override
    public List<SpecParam> list(SpecParam entity) {
        QueryWrapper<SpecParam> queryWrapper = Wrappers.<SpecParam>query();
        //根据分类id查询规格参数
        if (null != entity.getCid()) {
            queryWrapper.eq("cid_", entity.getCid());
        }
        if (null != entity.getSearching()) {
            queryWrapper.eq("searching_", entity.getSearching());
        }
        return getBaseMapper().selectList(queryWrapper);
    }
}

2.3.4 控制层

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/controller/SpecGroupController.java:

package com.lxs.legou.item.controller;

import com.lxs.legou.core.controller.BaseController;
import com.lxs.legou.core.po.ResponseBean;
import com.lxs.legou.item.po.SpecGroup;
import com.lxs.legou.item.service.ISpecGroupService;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Title:
 */
@RestController
@RequestMapping(value = "/group")
public class SpecGroupController extends BaseController<ISpecGroupService, SpecGroup> {
   

    @ApiOperation(value="保存规格参数", notes="保存规格参数")
    @PostMapping("/save-group")
    public ResponseBean saveGroup(@RequestBody List<SpecGroup> specGroup) throws Exception {
   
        ResponseBean rm = new ResponseBean();
        try {
   
            if (specGroup != null && specGroup.size() > 0) {
   
                service.saveGroup(specGroup.get(0).getCid(), specGroup);
            }
        } catch (Exception e) {
   
            e.printStackTrace();
            rm.setSuccess(false);
            rm.setMsg("保存失败");
        }
        return rm;
    }

}

legou-item/legou-item-service/src/main/java/com/lxs/legou/item/controller/SpecParamController.java:

package com.lxs.legou.item.controller;

import com.lxs.legou.core.controller.BaseController;
import com.lxs.legou.item.po.SpecParam;
import com.lxs.legou.item.service.ISpecParamService;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

/**
 * @Title:
 */
@RestController
@RequestMapping(value = "/param")
public class SpecParamController extends BaseController<ISpecParamService, SpecParam> {

    @ApiOperation(value="查询", notes="根据实体条件查询参数")
    @PostMapping(value = "/select-param-by-entity")
    public List<SpecParam> selectSpecParamApi(@RequestBody SpecParam entity) {
        return service.list(entity);
    }


}

2.3.5 功能演示

访问http://localhost:9005/item/group/list?cid=76

2.3.6 前端组件

再次强调,前端vue代码不是我们java程序员的菜,虽然有的小公司JAVA程序员也写前端代码,但是java程序员一般关注在后台Java的实现,前端vue代码了解掌握即可,不必要从0到1,一步一步的写出来,了解语法逻辑,真的有需要,写前端代码,能上手即可,这里规格参数前端组件逻辑比较复杂,给大家讲一下思路,拷贝即可

<template>
    <div>
        <Tree :data="rows"></Tree>
        <Modal v-model="modal" width="800px">
            <p slot="header">
                <span>{
  {category.title}}规格参数</span>
            </p>
            <Card v-for="(groupItem, groupIndex) in groups" :key="groupIndex">
                <p slot="title">
                    {
  {groupItem.name}}
                </p>
                <a href="#" slot="extra">
                    <Icon type="ios-trash" @click="deleteGroup(groupItem.id)"></Icon>
                </a>

                <Collapse>
                    <Panel v-for="(paramItem, paramIndex) in groupItem.params" :name="`${groupIndex}-${paramIndex}`" :key="paramIndex">
                        {
  {paramItem.name}}
                        <Icon type="ios-trash" style="float: right;margin-top: 10px" @click="deleteParam(groupItem.id, paramItem.id)"></Icon>
                        <div slot="content" style="height: 40px;">
                                <Col span="4">
                                    <Input v-model="paramItem.unit" placeholder="单位"></Input>
                                </Col>
                                <Col span="4" style="margin-left: 20px;">
                                    <Input v-model="paramItem.segments" placeholder="搜索范围"></Input>
                                </Col>
                                <Col  span="4" style="margin-top: 5px; margin-left: 20px;">
                                    数字参数:<i-switch v-model="paramItem.numeric"/>
                                </Col>
                                <Col span="4" style="margin-top: 5px; margin-left: 20px;">
                                    搜索参数:<i-switch v-model="paramItem.searching" />
                                </Col>
                                <Col span="4" style="margin-top: 5px; margin-left: 20px;">
                                    SKU参数:<i-switch v-model="paramItem.generic"/>
                                </Col>
                        </div>
                    </Panel>
                </Collapse>
                <br>
                <Button @click="addParam(groupItem.id)">添加新属性</Button>
            </Card>

            <div slot="footer">
                <Button type="primary" @click="saveGroup">保存规格模板</Button>
                <Button @click="addGroup">添加新分组</Button>
            </div>
        </Modal>
    </div>
</template>

<script>
    import {baseTree} from '@/libs/crud/base-tree'
    import {listToTree} from '@/libs/util'
    import instance from '@/libs/api/index'
    import Qs from 'qs'

    export default {
        mixins: [baseTree],
        // 表格各行的数据
        data() {
            return {
                modal: false, //分组对话框弹出标记
                category:{},//当前分类,弹出对话框是,显示的分类
                groups:[] //规格分组,格式[{id:1,cid:76,name:'主体',params:[...]},{}...]
            }
        },
        methods: {
            //保存规格参数
            saveGroup() {
                instance.post(`/item/group/save-group`,this.groups ,{
                    headers: {
                        'Content-Type': 'application/json;charset=UTF-8'
                    }
                }).then(response => {
                    this.$Message.success(response.data.msg)
                    this.modal = false
                }).catch(error => {
                    console.log(error)
                })
            },
            //删除参数
            deleteParam(gid, pid) {
                this.groups.find(item => item.id == gid).params.splice(
                    this.groups.find(item => item.id == gid).params.findIndex(item => item.id == pid), 1
                )
            },
            //添加新参数
            addParam(gid) {
                let pname = ''
                this.$Modal.confirm({
                    render: (h) => {
                        return h('Input', {
                            props: {
                                value: pname,
                                autofocus: true,
                                placeholder: '请输入分组名称'
                            },
                            on: {
                                input: (val) => {
                                    pname = val;
                                }
                            }
                        })
                    },
                    onOk: () => {
                        this.groups.find(item => item.id == gid).params.push({
                            name: pname,
                            cid: this.category.id,
                            groupId: gid
                        })
                    }
                })
            },
            //删除分组
            deleteGroup(gid) {
                this.groups.splice(this.groups.findIndex(item => item.id == gid), 1)
            },
            //添加新分组
            addGroup() {
                let gname = ''
                this.$Modal.confirm({
                    render: (h) => {
                        return h('Input', {
                            props: {
                                value: gname,
                                autofocus: true,
                                placeholder: '请输入分组名称'
                            },
                            on: {
                                input: (val) => {
                                    gname = val;
                                }
                            }
                        })
                    },
                    onOk: () => {
                        let mid = 0
                        if (this.groups && this.groups.length > 0) {
                            mid = this.groups.map(o => o.id).reduce((a,b) => {
                                return b > a ? b : a;
                            }) + 1
                        }
                        console.log(mid)
                        this.groups.push({id: mid, name: gname, cid: this.category.id, params:[]})
                    }
                })
            },
            // 查询分组
            queryGroup(cid) {
                instance.post(`/item/group/list`, Qs.stringify({"cid": cid})).then(response => {
                    this.groups = response.data;
                }).catch(error => {
                    console.log(error)
                })
                instance.get(`/item/category/edit/${cid}`).then(response => {
                    this.category = response.data;
                }).catch(error => {
                    console.log(error)
                })
            },
            // 查询分类
            query() {
                instance.post(`/item/category/list`).then(response => {
                    this.rows = this.readerLeaf(listToTree(response.data));
                }).catch(error => {
                    console.log(error)
                })
            },
            readerLeaf(data) {
                data.forEach(item => {
                    if (!item.children) {
                        item = Object.assign(item, {render: this.renderContent});
                    } else {
                        this.readerLeaf(item.children)
                    }
                });
                return data;
            },
            renderContent(h, {root, node, data}) {
                return h('span', {
                    style: {
                        display: 'inline-block',
                        width: '100%'
                    }
                }, [
                    h('span', [
                        h('Icon', {
                            props: {
                                type: 'ios-paper-outline'
                            },
                            style: {
                                marginRight: '8px'
                            }
                        }),
                        h('span', data.title)
                    ]),
                    h('span', {
                        style: {
                            display: 'inline-block',
                            float: 'right',
                            marginRight: '32px'
                        }
                    }, [
                        h('Button', {
                            props: Object.assign({}, this.buttonProps, {
                                icon: 'ios-aperture-outline'
                            }),
                            style: {
                                marginRight: '8px'
                            },
                            on: {
                                click: () => {
                                    this.queryGroup(data.id);
                                    this.modal = true
                                }
                            }
                        })
                    ])
                ]);
            }
        }

    }
</script>

前端发送的数据

目录
相关文章
|
13天前
|
安全 NoSQL Java
java电商项目(十)
本文介绍了电商系统中订单结算和下单流程的实现。主要包括: 1. **订单结页**: - **收件地址分析**:用户从购物车页面跳转到订单结算页,加载用户收件地址。地址信息存储在 `address_` 表中。 - **实现用户收件地址查询**:通过用户登录名查询收件地址,涉及实体类、DAO、Service 和 Controller 的实现。 2. **下单**: - **业务分析**:下单时创建订单数据,包括订单表 `order_` 和订单明细表 `order_item_`,同时修改商品库存、增加用户积分并删除购物车数据。
26 3
|
13天前
|
消息中间件 安全 Java
java电商项目(十一)
本文接续前几个文章的项目进行讲解!
24 1
|
13天前
|
缓存 NoSQL Java
java电商项目(十二)
本文接续前几个文章的项目进行讲解
80 1
|
13天前
|
存储 NoSQL Java
java电商项目(九)
本文介绍了购物车功能的实现过程,包括用户登录后将商品添加至购物车、购物车列表展示及微服务之间的认证机制。具体步骤如下: 1. **购物车功能**: - 用户选择商品并点击“加入购物车”,系统将商品信息存储到Redis中。 2. **微服务之间认证**: - **传递管理员令牌**:在授权中心微服务调用用户微服务时,生成管理员令牌并通过Header传递。 - **传递当前用户令牌**:用户登录后,通过Feign拦截器将用户令牌传递到其他微服务。 - **获取用户数据**:通过`SecurityContextHolder`获取用户信息,并使用公钥解密令牌以验证用户
21 1
|
13天前
|
监控 算法 Java
java电商项目(七)
微服务网关作为系统唯一对外的入口,位于客户端和服务端之间,处理非业务功能,如路由请求、鉴权、监控、缓存、限流等。它解决了客户端直接调用多个微服务带来的复杂性、跨域请求、认证复杂、难以重构等问题。常用的微服务网关技术有Nginx、Zuul和Spring Cloud Gateway。Spring Cloud Gateway因其集成断路器、路径重写和较好的性能而被广泛使用。本文介绍了如何使用Spring Cloud Gateway搭建后台网关系统,包括引入依赖、配置文件、跨域配置、路由过滤配置、负载均衡、限流等。此外,还详细讲解了RBAC权限数据管理、组织机构管理单点登录(SSO)及JWT鉴权的实现
19 1
|
13天前
|
canal 监控 JavaScript
java电商项目(六)
Thymeleaf 是一个类似于 FreeMarker 的模板引擎,能够完全替代 JSP。它支持动静结合,无网络时显示静态内容,有网络时用后台数据替换静态内容,并且与 Spring Boot 完美整合。本文介绍了如何使用 Thymeleaf 生成商品详情页的静态页面。具体步骤包括创建商品静态化微服务、配置项目依赖、创建 Controller 和 Service、生成静态页面、模板填充、静态资源过滤以及启动测试。此外,还介绍了如何通过 Canal 监听商品数据变化,自动触发静态页面的生成或删除。
24 1
|
13天前
|
SQL 自然语言处理 Java
java电商项目(五)
本文介绍了如何构建一个基于Elasticsearch的商品搜索微服务,主要包括以下几个部分: 1. **数据导入ES**: - 搭建搜索工程,创建`legou-search`项目,提供搜索服务和索引数据更新操作。 - 配置`pom.xml`文件,引入必要的依赖。 - 创建启动器和配置文件,配置Elasticsearch连接信息。 - 分析索引库数据格式,确定需要存储的数据字段。 - 实现商品微服务接口,调用其他微服务获取所需数据。 - 创建索引并导入数据,将SPU和SKU数据转换为索引库中的Goods对象。 2. **商品搜索*
22 1
|
13天前
|
canal NoSQL 关系型数据库
java电商项目(四)
本章介绍了如何通过Lua、OpenResty、Nginx限流及Canal的使用,实现电商门户首页的高并发解决方案。主要内容包括: 1. **商城门户搭建**:使用Vue和iView构建前端门户项目,介绍如何展示商品分类和广告数据,并通过Redis缓存提升访问速度。 2. **Lua基础**:介绍Lua的基本概念、特性、应用场景及安装步骤,并通过示例展示了Lua的基本语法和常用功能。 3. **OpenResty介绍**:详细说明OpenResty的特性和优势,包括如何安装OpenResty和配置Nginx,以及如何使用Lua脚本操作Nginx缓存和数据库。
17 1
|
13天前
|
存储 安全 Java
java电商项目(八)
OAuth 2.0 是一种开放标准,允许用户授权第三方应用访问其在某一网站上的私密资源,而无需提供用户名和密码。它通过提供一个令牌(token)来实现这一功能。OAuth 2.0 主要包括四种授权模式:授权码模式、简化模式、密码模式和客户端模式。授权码模式是最常用的一种,适用于第三方平台登录功能。Spring Security OAuth 2.0 提供了强大的工具来实现授权服务器和资源服务器的集成,支持多种授权模式和令牌存储方式,如内存、数据库、JWT 和
35 0
|
13天前
|
前端开发 算法 JavaScript
java电商项目(三)
本文介绍了乐购商城的商品数据分析和管理功能。首先解释了SPU(标准产品单位)和SKU(库存量单位)的概念,以及它们在商品管理和销售中的作用。接着详细分析了SPU、SPU详情和SKU三个表的结构及其关系。文章还介绍了商品管理的需求分析、实现思路和后台代码,包括实体类、持久层、业务层和控制层的实现。最后,文章讲解了前端组件的设计和实现,包括列表组件、添加修改组件、商品描述、通用规格、SKU特有规格和SKU列表的处理。通过这些内容,读者可以全面了解乐购商城的商品管理和数据分析系统。
26 0