首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >猿实战07——属性库之实现电商系统基石模块

猿实战07——属性库之实现电商系统基石模块

作者头像
山旮旯的胖子
发布2020-09-05 23:22:11
6340
发布2020-09-05 23:22:11
举报
文章被收录于专栏:猿人工厂猿人工厂猿人工厂

上一章节,猿人君教会了你一个不一样的地址管理,体验了代码生成器。一点一点引导你去设计和实现地址管理的功能模块。

今天,猿人工厂君就带你来一步一步实现,电商系统中,属于基石地位的模块——属性库。

寻找系统中的基石

在聊属性库之前,我们先来聊一聊,为什么说属性库在系统中处于基石地位?

每一个系统,都有自己独有的业务,在大家的职业生涯中,会去搭建各式各样的系统,而不是仅仅去搭建电商系统。猿人君真心希望大家学习电商系统的搭建之后,能够触类旁通,有能力肚子搭建属于你自己的系统。

在搭建系统的过程中,你会慢慢的发现一个有意思的事情,有些模块,它负责提供系统的基础数据,粒度很小,但是,系统中的其它数据却是通过这些粒度很小的数据,去变化成为了系统内的核心数据。

比如,我们今天要去搭建的属性库模块,就具备这样的特性——电商系统最终的目的是售卖商品,而商品是由什么来组成的呢?

你看,对于一件衣服而言,有“颜色”和“尺码”,对于手机而言,也有“颜色”和“版本”之分。你去看不同的商品,你会发现会有各式各样的用于区分商品特性的东西。

这类用于描述和区分商品特性的东西,很繁杂,他们可能具有不同的含义,比如“版本”,有64G\128G\256G之分。我们给颜色和版本定义了一个抽象的名字——就叫做属性,而64G\128G\256G这样的含义,就是属性值。

越是观察商品,你越会明白一个道理,商品的构成有这样一个简单的关系:

一个个属性值和属性,就像沙粒一样堆砌,最终形成一个集合去描述商品,属于描述商品的最基本的元素。

像这样的元素,就是你系统中最基本的元素,像这样的模块,就是你系统中的基石模块。系统的核心数据往往就是这样由小而大,聚沙成塔,通过寻找系统中的基数元素,能够让你更容易的去搭建属于你自己的系统。

功能概览

在之前的文章猿设计6——真电商之属性的套路你了解吗中,我们其实已经分析过属性库的设计了,接下来我们一起看看,根据这些设计整理出来的一些功能。

在属性库模块中,需要提供,属性、属性值、属性组、的列表以及新增/编辑功能,在属性列表,点击管理按钮,进入到当前属性的属性值列表页面。在属性列表,点击“组管理”则切换到属性组管理页面。在属性组和属性值列表页面,分别提供对应的新增/编辑功能。

数据库设计

我们之前的设计文章中,已经整理过属性库的相关属性了,我们根据之前的设计,将这些设计落地为数据库的物理设计,大家可以看一下。

属性库整体前端

我们之前可以看到,属性库管理界面中,同时需要提供维护属性、属性值、属性组的相关维护功能。这些功能如果出现在同一个页面,那么可以预见的是,最终我们的页面将变得庞大无比,最终难以维护。

还记得之前我们讲过的地址管理功能吗?我们怎么处理的?活学活用,是一件很重要的事情,我们可以参考下之前的实现思路——将属性、属性值、属性组分别定义成小的组件。最后,由一个view来组织和整合它们就好了。

<template>
  <div id="attributeLibraryDiv"class="app-container">
    <el-tabs v-model="activeName"type="border-card" @tab-click="tabChange">
      <el-tab-pane label="属性管理"name="attribute">
        <div v-if="attribute">
          <attributeListSearchref="attributeListSearch" @selectedAttribute="selectedAttribute"/>
        </div>
        <divv-if="attributeValue">
          <attributeValueListSearchref="attributeValueListSearch" :pid="propertyId"@returnBack="returnBack" />
        </div>
      </el-tab-pane>
      <el-tab-pane label="组管理"name="group">
        <div v-if="group">
          <groupManagementref="groupManagement" />
        </div>
      </el-tab-pane>
    </el-tabs>
  </div>
</template>
 
<script>
import attributeListSearch from'@/components/productManage/attributeListSearch'
import attributeValueListSearchfrom '@/components/productManage/attributeValueListSearch'
import groupManagement from'@/components/productManage/groupManagement'
export default {
  components: {
    attributeListSearch,
    attributeValueListSearch,
    groupManagement
  },
  data() {
    return {
      // 属性值管理
      attributeValue: false,
      // 属性管理
      attribute: false,
      // 组管理
      group: false,
      activeName: 'attribute',
      propertyId: 0
 
    }
  },
  created() {
    // 默认选中第一个
    this.attribute = true
  },
  methods: {
    // 管理
    selectedAttribute(row) {
      this.attribute = false
      this.attributeValue = true
      this.propertyId = row.propertyId
      console.log(row.propertyId)
    },
    // 回退
    returnBack() {
      this.attribute = true
      this.attributeValue = false
    },
    tabChange(tab, event) {
      if (tab.name === 'attribute') {
        this.attribute = true
        this.group = false
      } else if (tab.name === 'group') {
        this.attribute = false
        this.group = true
      }
    }
  }
}
</script>
 
<style scoped>
</style>

在上一章节中,我们已经讲过父子组件的参数传递方式,在这里就不一一讲解了。

属性组后端实现

像这样相对复杂的功能模块,在进入实现时,可以本着先易后难的原则出发,逐步实现。属性组的功能相对简单,所以我们先从属性组的实现入手。

由于之前已经给出了我们自己定义的代码生成器,属性组的实现也相对简单,考虑到篇幅问题,这一部分我们给出Controller层面的功能实现,service、和dao,还是希望你自行实现,在初学时期,多谢代码,对你熟悉掌握代码编写的技巧,是一个必不可少的环节。

/**
 * Copyright(c) 2004-2020 pangzi
 * com.pz.basic.mall.controller.sys.MallPropertyGroupController.java
 */
package com.pz.basic.mall.controller.product.property;
 
import com.pz.basic.mall.domain.base.Result;
 
import com.pz.basic.mall.domain.base.enums.DataStatusEnum;
import com.pz.basic.mall.domain.product.property.MallPropertyGroup;
import com.pz.basic.mall.domain.product.property.query.QueryMallPropertyGroup;
import com.pz.basic.mall.service.product.property.MallPropertyGroupService;
 
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;
 
 
/**
 *
 * @author pangzi
 * @date 2020-06-22 20:47:27
 *
 *
 */
@RestController
@RequestMapping("/propertyGroup")
public class MallPropertyGroupController {
 
 
    private MallPropertyGroupService mallPropertyGroupService;
 
    public void setMallPropertyGroupService(MallPropertyGroupService mallPropertyGroupService){
        this.mallPropertyGroupService =mallPropertyGroupService;
    }
 
 
    /**
     * 新增属性组
     * @param mallPropertyGroup
     * @return
     */
   @RequestMapping("/addMallPropertyGroup")
    public Result<MallPropertyGroup>  addMallPropertyGroup(@RequestBodyMallPropertyGroup mallPropertyGroup){
        try{
            returnmallPropertyGroupService.addMallPropertyGroup(mallPropertyGroup);
        }catch(Exception e){
            e.printStackTrace();
            return new Result(false);
        }
    }
 
    /**
     * 根据ID查找属性组
     * @param id
     * @return
     */
   @RequestMapping("/findMallPropertyGroupById")
    public Result<MallPropertyGroup> findMallPropertyGroupById(Long id){
        returnmallPropertyGroupService.getMallPropertyGroupById(id.intValue());
    }
 
    /**
     * 修改属性组
     * @param mallPropertyGroup
     * @return
     */
   @RequestMapping("/updateMallPropertyGroup")
    public Result updateMallPropertyGroup(@RequestBody MallPropertyGroup mallPropertyGroup){
        try{
            return mallPropertyGroupService.updateMallPropertyGroupById(mallPropertyGroup);
        }catch(Exception e){
            e.printStackTrace();
            return new Result(false);
        }
    }
 
 
    /**
     * 分页返回属性组列表
     * @param queryMallPropertyGroup
     * @return
     */
    @RequestMapping("/findByPage")
    public Result<List<MallPropertyGroup>> findByPage(@RequestBodyQueryMallPropertyGroup queryMallPropertyGroup){
        returnmallPropertyGroupService.getMallPropertyGroupsByPage(queryMallPropertyGroup);
    }
 
 
}

属性组前端实现

聊完了后端数据接口的事情,我们一起来看看前端实现的问题,考虑到大部分朋友前端并不是很熟悉,我们再讲讲属性组前端组件的封装。

我们先封装访问后端的数据接口:

export functionfetchPropertyGroupList(data) {
  return request({
    url: '/propertyGroup/findByPage',
    method: 'post',
    data
  })
}
 
export functioncreatePropertyGroup(data) {
  return request({
    url: '/propertyGroup/addMallPropertyGroup',
    method: 'post',
    data: data
  })
}
 
export functionupdatePropertyGroup(data) {
  return request({
    url: '/propertyGroup/updateMallPropertyGroup',
    method: 'post',
    data: data
  })
}

然后在组件里引用它。

考虑到你还是不太熟悉整个页面的开发,再次将前端页的整体代码给到你。

<template>
  <div id="groupManagementDiv"class="app-container">
    <el-button type="primary"icon="el-icon-edit" style="float:right;margin-bottom:20px;"@click="addDate()">新增组</el-button>
    <divclass="table-container">
      <el-table
        ref="table"
        v-loading="listLoading"
        :data="list"
        style="width: 100%"
        border
      >
        <el-table-column label="组ID"align="center">
          <templateslot-scope="scope">{{ scope.row.groupId }}</template>
        </el-table-column>
        <el-table-column label="属性组名"align="center">
          <templateslot-scope="scope">{{ scope.row.groupName }}</template>
        </el-table-column>
        <el-table-column label="排序"align="center">
          <templateslot-scope="scope">{{ scope.row.sortOrder }}</template>
        </el-table-column>
        <el-table-column label="状态"align="center">
          <templateslot-scope="scope">{{ scope.row.status == 1 ? "启用" :"停用" }}</template>
        </el-table-column>
        <el-table-column label="类型"align="center">
          <templateslot-scope="scope">{{ scope.row.groupType == 1 ?"属性":"属性值" }}</template>
        </el-table-column>
        <el-table-column label="操作"width="200">
 
          <templateslot-scope="scope">
            <el-button
              type="primary"
              size="mini"
             @click="handleUpdate(scope.row)"
            >修改
            </el-button>
          </template>
        </el-table-column>
      </el-table>
    </div>
    <paginationv-show="total>0" :total="total":page.sync="listQuery.page" :limit.sync="listQuery.pageSize"@pagination="getList" />
    <!-- 新增/编辑弹框 -->
    <el-dialog:title="textMap[dialogStatus]":visible.sync="dialogFormVisible">
      <el-form ref="dataForm":rules="rules" :model="temp"label-position="right" label-width="120px" style="width:320px; margin-left:50px;">
        <el-form-item label="属性组名:"prop="groupName">
          <el-inputv-model="temp.groupName" placeholder="请输入属性组名"/>
        </el-form-item>
        <el-form-item label="组排序:"prop="sort">
          <el-input-number
            v-model="temp.sortOrder"
            :min="0"
            :max="100"
            placeholder="请输入组排序"
          />
        </el-form-item>
        <el-form-item label="类型:"prop="groupType">
          <el-selectv-model="temp.groupType" placeholder="请选择">
            <el-option
              v-for="item ingroupTypeList"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
        <el-form-item label="组状态:"prop="status">
          <el-selectv-model="temp.status" placeholder="请选择">
            <el-option
              v-for="item invalueList"
              :key="item.value"
              :label="item.label"
              :value="item.value"
            />
          </el-select>
        </el-form-item>
      </el-form>
      <div slot="footer"class="dialog-footer">
        <el-button@click="dialogFormVisible = false">
          取消
        </el-button>
        <el-button type="primary"@click="dialogStatus==='create'?createData():updateData()">
          确定
        </el-button>
      </div>
    </el-dialog>
  </div>
</template>
 
<script>
import Pagination from'@/components/Pagination' // secondary package based on el-pagination
import {fetchPropertyGroupList, createPropertyGroup, updatePropertyGroup } from'@/api/propertyManage/propertyManage'
export default {
  components: { Pagination },
  data() {
    return {
      // 弹框校验规则
      rules: {
        groupName: [{ required: true, message:'请输入属性组名',trigger: 'change' }],
        status: [{ required: true, message: '请选择属性组状态',trigger: 'change' }],
        groupType: [{ required: true, message:'请选择属性组类型',trigger: 'change' }],
        sortOrder: [{ required: true, message:'sort is required', trigger: 'blur' }]
      },
      temp: {
        groupId: undefined,
        // 组中文名:
        groupName: '',
        // 组状态
        status: 1,
        // 排序
        sortOrderort: 0,
        // 组类型
        groupType: 1
      },
      // 弹框是否显示
      dialogFormVisible: false,
      dialogStatus: '',
      textMap: {
        update: '组新增',
        create: '组修改'
      },
      // table集合
      list: null,
      multipleSelection: [],
      // 状态
      valueList: [{
        value: 1,
        label: '启用'
      }, {
        value: 0,
        label: '停用'
      }],
      // 所属组
      groupTypeList: [{
        value: 1,
        label: '属性'
      }, {
        value: 2,
        label: '属性值'
      }],
      // 分页
      total: 0,
      // loading
      listLoading: true,
      listQuery: {
        page: 1,
        paziSize: 10,
        groupType: 1
      }
    }
  },
  created() {
    // 列表查询
    this.getList()
  },
  methods: {
    // 更新保存方法
    updateData() {
      this.$refs['dataForm'].validate((valid)=> {
        if (valid) {
          const tempData = Object.assign({},this.temp)
          updatePropertyGroup(tempData).then(()=> {
            const index = this.list.findIndex(v=> v.id === this.temp.id)
            this.list.splice(index, 1,this.temp)
            this.dialogFormVisible = false
            this.$notify({
              title: 'Success',
              message: 'Update Successfully',
              type: 'success',
              duration: 2000
            })
          })
        }
      })
    },
    // 创建保存方法
    createData() {
      this.$refs['dataForm'].validate((valid)=> {
        if (valid) {
         createPropertyGroup(this.temp).then((res) => {
            this.temp.groupId =res.model.groupId
            this.list.unshift(this.temp)
            this.dialogFormVisible = false
            this.$notify({
              title: 'Success',
              message: 'Created Successfully',
              type: 'success',
              duration: 2000
            })
          })
        }
      })
    },
    // 编辑
    handleUpdate(row) {
      this.temp = Object.assign({}, row) //copy obj
      console.log(this.temp)
      this.dialogStatus = 'update'
      this.dialogFormVisible = true
      this.$nextTick(() => {
        this.$refs['dataForm'].clearValidate()
      })
    },
    // 列表查询
    getList() {
      this.listLoading = true
     fetchPropertyGroupList(this.listQuery).then(response => {
        this.list = response.model
        this.total = response.totalItem
 
        // Just to simulate the time of therequest
        setTimeout(() => {
          this.listLoading = false
        }, 1.5 * 1000)
      })
    },
    // 重置
    resetTemp() {
      this.temp = {
        groupId: undefined,
        // 组中文名:
        groupName: '',
        // 组状态
        status: 1,
        // 排序
        sort: 0,
        // 适用对象
        groupType: 1
      }
    },
    // 新增
    addDate() {
      this.resetTemp()
      this.dialogStatus = 'create'
      this.dialogFormVisible = true
      this.$nextTick(() => {
        this.$refs['dataForm'].clearValidate()
      })
    }
  }
}
</script>
 
<style scoped>
</style>

到目前为止,属性组管理功能我们就开发完毕了。大家一定要自己动手去实际操作,记得多多联调和测试噢。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-08-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 猿人工厂 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档