1.数据库表设计
1.商品属性表
use sunliving_commodity;
CREATE TABLE commodity_attr
(
attr_id BIGINT NOT NULL AUTO_INCREMENT COMMENT '属性 id' ,
attr_name CHAR ( 30 ) COMMENT '属性名' ,
search_type TINYINT COMMENT '是否需要检索[0-不需要,1-需要]' ,
icon VARCHAR ( 255 ) COMMENT '图标' ,
value_select CHAR ( 255 ) COMMENT '可选值列表[用逗号分隔]' ,
attr_type TINYINT COMMENT '属性类型[0-销售属性,1-基本属性]' ,
ENABLE BIGINT COMMENT '启用状态[0 - 禁用,1 - 启用]' ,
category_id BIGINT COMMENT '所属分类' ,
show_desc TINYINT COMMENT '快速展示【是否展示在介绍上;0-否 1-是】' ,
PRIMARY KEY ( attr_id)
) CHARSET = utf8mb4 COMMENT = '商品属性表' ;
SELECT *
FROM ` commodity_attr`
2.renren-generator生成CRUD
1.基本配置检查
1.generator.properties
2.application.yml
2.启动RenrenGeneratorApplication.java生成CRUD
1.启动后访问localhost:81
2.生成商品属性表的crud
3.将crud代码集成到项目中
1.解压,找到main目录
2.将main目录替换为sunliving-commodity模块的main目录
4.检查代码
1.将AttrController.java的@RequiresPermissions注解注释掉
2.application.yml
3.AttrEntity.java
4.AttrDao.java
5.AttrDao.xml
6.AttrService.java
7.AttrServiceImpl.java
注入容器,实现AttrService,继承ServiceImpl
8.AttrController.java
9.测试接口
1.http://localhost:5050/api/sunliving-commodity/commodity/attr/list
2.http://localhost:5050/api/sunliving-commodity/commodity/attr/save
5.显示基本界面
1.将代码生成器生成的两个前端页面复制到前端项目
2.修改attr.vue的名字为baseattr.vue因为后面还有一个销售属性的页面
3.创建路由为 commodity/baseattr 的菜单
4.修改baseattr.vue的所有请求(2个)为 环境变量 + 资源路径 的方式(由于没有更换模块所以gateway和多环境不需区分)
5.修改 attr-add-or-update.vue(两个请求)
6.测试基本界面的crud,没问题
3.添加商品属性
1.基本页面搭建
1.将属性类型改成下拉框
1.找到element-ui下拉框组件位置 https://element.eleme.cn/#/zh-CN/component/select#methods
2.修改后的el-form-item
el-option的属性
v-model="dataForm.attrType"表示绑定的是dataForm.attrType这个属性
<el-form-item label="属性类型[0-销售属性,1-基本属性]" prop="attrType">
<!-- <el-input v-model="dataForm.attrType" placeholder="属性类型[0-销售属性,1-基本属性]"></el-input>-->
<el-select v-model="dataForm.attrType" placeholder="请选择">
<el-option
label="销售属性"
:value="0">
</el-option>
<el-option
label="基本属性"
:value="1">
</el-option>
</el-select>
</el-form-item>
3.结果展示
2.将所属分类改成级联菜单
1.修改后的el-form-item
<el-form-item label="所属分类" prop="categoryId">
<!-- <el-input v-model="dataForm.categoryId" placeholder="所属分类"></el-input>-->
<el-cascader
filterable
placeholder="请选择"
v-model="cascadedCategoryId"
:options="categories"
:props="props"
></el-cascader>
</el-form-item>
2.数据池
categories : [ ] ,
cascadedCategoryId : [ ] ,
props : {
value : 'id' ,
label : 'name' ,
children : 'childrenCategories' ,
expandTrigger : 'hover'
}
3.方法池
getCategories ( ) {
this . $http ( {
url : process. env. COMMODITY_BASEPATH + '/commodity/category/list/tree' ,
method : 'get'
} ) . then ( ( { data} ) => {
console. log ( data. data)
this . categories = data. data;
} )
}
4.created调用
created ( ) {
this . getCategories ( )
}
5.结果展示
6.dataForm的categoryId默认值设置为0,用户如果不填的话就没有所在分组
4.销售属性与属性组的关联表
1.关联表设计
use sunliving_commodity;
CREATE TABLE commodity_attr_attrgroup_relation
(
id BIGINT NOT NULL AUTO_INCREMENT COMMENT 'id' ,
attr_id BIGINT COMMENT '属性 id' ,
attr_group_id BIGINT COMMENT '属性分组 id' ,
attr_sort INT COMMENT '属性组内排序' ,
PRIMARY KEY ( id)
) CHARSET = utf8mb4 COMMENT = '商品属性和商品属性组的关联表' ;
SELECT *
FROM ` commodity_attr_attrgroup_relation`
2.代码生成器生成crud
1.生成代码
2.测试 http://localhost:5050/api/sunliving-commodity/commodity/attrattrgrouprelation/list
5.添加基本属性 attr-add-or-update.vue
1.选择所属分类,联动显示所属分组
1.所属分类下新增所属分组下拉框
<el-form-item label="所属分组">
<el-select ref="groupSelect" v-model="dataForm.attrGroupId" placeholder="请选择">
<el-option
v-for="item in attrGroups"
:key="item.id"
:label="item.name"
:value="item.id">
</el-option>
</el-select>
</el-form-item>
2.数据池新增attrGroups属性,表示属性组信息
3.数据池的dataForm新增attrGroupId属性
4.方法池前面新增一个watch,监控所属分类的id,一旦变化就根据这个id向后端发送请求得到该所属分类的所有分组
watch : {
cascadedCategoryId ( path ) {
this . attrGroups = [ ] ;
this . dataForm. attrGroupId = "" ;
this . dataForm. categoryId = path[ path. length - 1 ] ;
if ( path && path. length == 3 ) {
this . $http ( {
url : process. env. COMMODITY_BASEPATH + '/commodity/attrgroup/list/' + path[ path. length - 1 ] ,
method : "get" ,
params : this . $http. adornParams ( { page : 1 , limit : 10000000 } )
} ) . then ( ( { data} ) => {
console. log ( "data=" , data. page. list)
if ( data && data. code === 0 ) {
this . attrGroups = data. page. list;
} else {
this . $message. error ( data. msg) ;
}
} ) ;
} else if ( path. length == 0 ) {
this . dataForm. categoryId = "" ;
} else {
this . $message. error ( "请选择正确的分类" ) ;
this . dataForm. categoryId = "" ;
}
}
} ,
5.表单提交时新增属性分组属性
2.后端 sunliving-commodity 模块
1.AttrEntity.java新增attrGroupId属性
2.service层
1.AttrService.java 新增方法,保存基本属性以及与属性分组的关联关系
void saveAttrAndRelation ( AttrEntity attr) ;
2.AttrServiceImpl.java 实现方法
@Transactional
@Override
public void saveAttrAndRelation ( AttrEntity attr) {
this . save ( attr) ;
if ( attr. getAttrGroupId ( ) != null && attr. getAttrType ( ) == 1 ) {
AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity ( ) ;
relationEntity. setAttrId ( attr. getAttrId ( ) ) ;
relationEntity. setAttrGroupId ( attr. getAttrGroupId ( ) ) ;
attrAttrgroupRelationService. save ( relationEntity) ;
}
}
3.controller层
AttrController.java 修改save方法
@RequestMapping ( "/save" )
public R save ( @RequestBody AttrEntity attr) {
attrService. saveAttrAndRelation ( attr) ;
return R . ok ( ) ;
}
4.结果展示
1.添加一个测试属性到节能灯的测试分组下
2.查看属性表
3.查看关联表
6.基本属性分页查询
1.后端
1.首先确保引入了分页插件
package com. sun. sunliving. commodity. config ;
import com. baomidou. mybatisplus. extension. plugins. PaginationInterceptor ;
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.sun.sunliving.commodity.dao" )
public class MyBatisConfig {
@Bean
public PaginationInterceptor paginationInterceptor ( ) {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor ( ) ;
paginationInterceptor. setOverflow ( true ) ;
paginationInterceptor. setLimit ( 100 ) ;
return paginationInterceptor;
}
}
2.service层
1.AttrService.java
PageUtils queryPage ( Map < String , Object > params) ;
2.AttrServiceImpl.java 根据参数中的params的key构造查询条件为id等于key或者name like key
@Override
public PageUtils queryPage ( Map < String , Object > params) {
String key = ( String ) params. get ( "key" ) ;
QueryWrapper < AttrEntity > attrEntityQueryWrapper = new QueryWrapper < > ( ) ;
attrEntityQueryWrapper. eq ( "attr_type" , 1 ) ;
if ( StringUtils . isNotBlank ( key) ) {
attrEntityQueryWrapper. and ( wrapper -> {
wrapper. eq ( "attr_id" , key) . or ( ) . like ( "attr_name" , key) ;
} ) ;
}
IPage < AttrEntity > page = this . page (
new Query < AttrEntity > ( ) . getPage ( params) ,
attrEntityQueryWrapper
) ;
return new PageUtils ( page) ;
}
3.AttrController.java
@RequestMapping ( "/list" )
public R list ( @RequestParam Map < String , Object > params) {
PageUtils page = attrService. queryPage ( params) ;
return R . ok ( ) . put ( "page" , page) ;
}
2.前端
只需要注意脚手架请求携带的参数即可,key为输入框的内容
3.结果展示
1.根据id查询
2.根据属性名查询
7.完成销售属性的维护
1.后端初始化
1.分页查询
1.AttrService.java 新增方法
PageUtils queryPageOnSale ( Map < String , Object > params) ;
2.AttrServiceImpl.java 实现方法,修改一下attr_type为0 即可
@Override
public PageUtils queryPageOnSale ( Map < String , Object > params) {
String key = ( String ) params. get ( "key" ) ;
QueryWrapper < AttrEntity > attrEntityQueryWrapper = new QueryWrapper < > ( ) ;
attrEntityQueryWrapper. eq ( "attr_type" , 0 ) ;
if ( StringUtils . isNotBlank ( key) ) {
attrEntityQueryWrapper. and ( wrapper -> {
wrapper. eq ( "attr_id" , key) . or ( ) . like ( "attr_name" , key) ;
} ) ;
}
IPage < AttrEntity > page = this . page (
new Query < AttrEntity > ( ) . getPage ( params) ,
attrEntityQueryWrapper
) ;
return new PageUtils ( page) ;
}
3.AttrController.java 新增方法
@RequestMapping ( "/listOnSale" )
public R listOnSale ( @RequestParam Map < String , Object > params) {
PageUtils page = attrService. queryPageOnSale ( params) ;
return R . ok ( ) . put ( "page" , page) ;
}
4.重启测试
2.前端初始化
1.接入页面
1.粘贴一份baseattr.vue和attr-add-or-update.vue并修改名字
2.创建菜单
3.修改导入的组件为 sale-attr-add-or-update
4.修改分页查询的请求为listOnSale
5.此时应该无数据
3.销售属性添加
1.修改sale-attr-add-or-update.vue的对话框
<el-dialog
:title="!dataForm.attrId ? '新增' : '修改'"
:close-on-click-modal="false"
:visible.sync="visible">
<el-form :model="dataForm" :rules="dataRule" ref="dataForm" @keyup.enter.native="dataFormSubmit()"
label-width="80px">
<el-form-item label="属性名" prop="attrName">
<el-input v-model="dataForm.attrName" 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="可选值列表[用逗号分隔]" prop="valueSelect">
<el-input v-model="dataForm.valueSelect" placeholder="可选值列表[用逗号分隔]"></el-input>
</el-form-item>
<el-form-item label="属性类型[0-销售属性,1-基本属性]" prop="attrType">
<!-- <el-input v-model="dataForm.attrType" placeholder="属性类型[0-销售属性,1-基本属性]"></el-input>-->
<el-select v-model="dataForm.attrType" placeholder="请选择">
<el-option
label="销售属性"
:value="0">
</el-option>
<el-option
label="基本属性"
:value="1">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="启用状态[0 - 禁用,1 - 启用]" prop="enable">
<el-input v-model="dataForm.enable" placeholder="启用状态[0 - 禁用,1 - 启用]"></el-input>
</el-form-item>
<el-form-item label="所属分类" prop="categoryId">
<el-cascader
filterable
placeholder="请选择"
v-model="cascadedCategoryId"
:options="categories"
:props="props"
></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>
2.修改watch监听,只保留一句
3.目前如果新增一条记录,将不会保存关联关系,只会保存传进去的基本属性
4.新增一条记录测试
5.修改saleattr.vue,删除几个不必要的表头
4.点击修改,所属分类不回显问题
1.问题引出
2.找到前端点击修改的逻辑进行分析
1.点击修改,跳转到这个init方法,并携带id
2.这个后端接口,根据id获取一条记录然后直接返回,但是如果要显示级联菜单则需要一个id列表
3.service层编写方法,根据所属分类id,找出所有父分类的id
1.AttrService.java
List < Long > getCascadedCategoryId ( Long categoryId) ;
2.AttrServiceImpl.java
@Override
public List < Long > getCascadedCategoryId ( Long categoryId) {
ArrayList < Long > res = new ArrayList < > ( ) ;
CategoryEntity categoryEntity = categoryDao. selectById ( categoryId) ;
while ( categoryEntity != null ) {
res. add ( categoryEntity. getId ( ) ) ;
categoryId = categoryEntity. getParentId ( ) ;
categoryEntity = categoryDao. selectById ( categoryId) ;
}
Collections . reverse ( res) ;
return res;
}
4.AttrEntity.java新增一个级联菜单的id属性,用于controller返回数据
5.AttrController.java返回数据
@RequestMapping ( "/info/{attrId}" )
public R info ( @PathVariable ( "attrId" ) Long attrId) {
AttrEntity attr = attrService. getById ( attrId) ;
List < Long > cascadedCategoryId = attrService. getCascadedCategoryId ( attr. getCategoryId ( ) ) ;
attr. setCascadedCategoryId ( cascadedCategoryId) ;
return R . ok ( ) . put ( "attr" , attr) ;
}
6.前端sale-attr-add-or-update.vue修改init方法,回显级联菜单
7.重启测试
8.关于该阶段多表查询的小结
1.E-R图
2.外键解决方式
一对一,一对多一般通过外键解决 通过在多方的表中添加一个外键字段指向一方的主键来实现 此时这个外键的值只能从一方的主键中取,在该项目中,使用的是级联菜单,让用户只能选择一方的主键
3.关联表解决方式
一对多,多对多一般通过关联表解决 关联表具有两个表的主键,也可以有两个表name或者其他的冗余字段 在该项目的基本属性保存功能,向属性的entity传入了一个属性组的id,使其在保存时同时完成了属性组与基本属性的关联
4.新增一个表的思路分析
1.是否有关联?
2.怎么体现关联?
3.怎么实现关联?
4.基本属性的新增功能的思路分析
有两个关联,分类表与属性表的一对多,属性组表与属性表的一对多 分类表与属性表:使用在属性表中添加外键的方式体现关联 属性组表与属性表:使用关联表的方式体现关联 分类表与属性表:在新增时使用级联菜单的形式让用户选择分类表的主键 属性组表与属性表,在新增时实现关联
使用vue的watch监控分类的变化 只要用户选中了所属分类,则取出该分类,根据这个分类的id来找到所有的分组,并以下拉框的形式显示 点击确定,就将这个新增的所属分组的id也保存到entity中(需要新增加字段) 后端首先将基本的属性信息保存到表中,然后根据所属分组的id和销售属性的id,将数据插入到关联表中