上一章节,猿人君教会了你一个不一样的地址管理,体验了代码生成器。一点一点引导你去设计和实现地址管理的功能模块。
今天,猿人工厂君就带你来一步一步实现,电商系统中,属于基石地位的模块——属性库。
寻找系统中的基石
在聊属性库之前,我们先来聊一聊,为什么说属性库在系统中处于基石地位?
每一个系统,都有自己独有的业务,在大家的职业生涯中,会去搭建各式各样的系统,而不是仅仅去搭建电商系统。猿人君真心希望大家学习电商系统的搭建之后,能够触类旁通,有能力肚子搭建属于你自己的系统。
在搭建系统的过程中,你会慢慢的发现一个有意思的事情,有些模块,它负责提供系统的基础数据,粒度很小,但是,系统中的其它数据却是通过这些粒度很小的数据,去变化成为了系统内的核心数据。
比如,我们今天要去搭建的属性库模块,就具备这样的特性——电商系统最终的目的是售卖商品,而商品是由什么来组成的呢?
你看,对于一件衣服而言,有“颜色”和“尺码”,对于手机而言,也有“颜色”和“版本”之分。你去看不同的商品,你会发现会有各式各样的用于区分商品特性的东西。
这类用于描述和区分商品特性的东西,很繁杂,他们可能具有不同的含义,比如“版本”,有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>
到目前为止,属性组管理功能我们就开发完毕了。大家一定要自己动手去实际操作,记得多多联调和测试噢。