一、sku相关概念
1、什么是sku??
SKU=Stock Keeping Unit(库存量单位).一个sku能够确定有多少库存及什么价格 --> 相当于是条形码.
什么又是spu??
SPU = Standard Product Unit (标准产品单位)SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
例如:iPhone X 可以确定一个产品即为一个SPU。
2、sku属性及选项
一个sku能够确定库存及价格.而且知道价格会被多个sku属性影响.而且每个sku属性都是有多个sku属性选项.
一个商品有多个sku,sku的组成是由多个sku属性及它们的sku属性选项进行组合产生 注:我们买卖的sku,不是商品!
xxx旗袍 -sku
sku属性:颜色,和尺寸
sku属性选项: 颜色(红色,白色),尺寸(xl,xxl)
skus:红色xl,红色xxl.白色xl,白色xxl
3、什么是商品SKU?
商品SKU实际上就是SKU,为了避免误解和SKU属性混淆,我用商品SKU来命名,表示从属于商品的、实际销售和存储的子实体。
一个商品SKU,表示该商品关联的若干SKU属性的的属性值的某个组合所形成的子实体。
如对应上面的例子,其中的一种组合 XL + 红色 就会形成一个商品SKU。然后,我们可以在该实体上管理价格、库存、专门的图片等信息。
4、商品变种(变异)
商品变异其实就是商品SKU,只不过在某些技术文章中这样定义了。即以“变异”来表达商品SKU的生成
5、属性集
用于管理各类扩展属性的集合,其中SKU属性也是在管理范畴之内。
商品通过关联属性集而获得该属性集设置好的SKU属性,然后才可以根据这些SKU属性生成商品SKU。
属性集也成为产品类型。
常见的属性集有:服装、PC、家具、图书等。
二、前后端代码实现
1、后端根据数据库生成如下表:
注:这里t_product中新增了一个medias字段,因此重新生成覆盖了之前的!
修改生成的代码:
对于前端显示时间问题解决:
2、zuul网关配置超时时间 解决前端fallback问题
3、完成后端代码
public interface ProductMapper extends BaseMapper<Product> {
List<Product> loadPageData(Page<Product> page, ProductQuery query); //加载分页数据
}
public interface IProductService extends IService<Product> {
PageList<Product> selectPageList(ProductQuery query);//跨表高级分页
}
@Service
public class ProductServiceImpl extends ServiceImpl<ProductMapper, Product> implements IProductService {
@Autowired
private ProductMapper productMapper;
@Autowired
private ProductExtMapper productExtMapper;
@Override
public PageList<Product> selectPageList(ProductQuery query) {
Page<Product> page = new Page<>(query.getPage(),query.getRows());
List<Product> data = productMapper.loadPageData(page,query);
long total = page.getTotal();
return new PageList<>(total,data);
}
@Override
public boolean insert(Product entity) {
//添加本表信息以外,还要存放关联表
entity.setCreateTime(new Date().getTime());
productMapper.insert(entity);
System.out.println(entity.getId());
if (entity.getProductExt() != null) {
entity.getProductExt().setProductId(entity.getId());
productExtMapper.insert(entity.getProductExt());
}
return true;
}
@Override
public boolean updateById(Product entity) {
//添加本表信息以外,还要存放关联表
entity.setUpdateTime(new Date().getTime());
productMapper.updateById(entity);
//通过productId查询productExt
Wrapper<ProductExt> wrapper = new EntityWrapper<ProductExt>()
.eq("productId", entity.getId());
ProductExt productExt = productExtMapper.selectList(wrapper).get(0);
//把前台传递进来值设置给数据库查询出来值,并且把它修改进去
ProductExt tmp = entity.getProductExt();
if ( tmp!= null) {
productExt.setDescription(tmp.getDescription());
productExt.setRichContent(tmp.getRichContent());
productExtMapper.updateById(productExt);
}
return true;
}
}
<?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.zhengqing.aigou.mapper.ProductMapper">
<!-- 通用查询映射结果 -->
<resultMap id="BaseResultMap" type="com.zhengqing.aigou.domain.Product">
<id column="id" property="id" />
<result column="createTime" property="createTime" />
<result column="updateTime" property="updateTime" />
<result column="name" property="name" />
<result column="subName" property="subName" />
<result column="code" property="code" />
<result column="product_type_id" property="productTypeId" />
<result column="onSaleTime" property="onSaleTime" />
<result column="offSaleTime" property="offSaleTime" />
<result column="brand_id" property="brandId" />
<result column="state" property="state" />
<result column="maxPrice" property="maxPrice" />
<result column="minPrice" property="minPrice" />
<result column="saleCount" property="saleCount" />
<result column="viewCount" property="viewCount" />
<result column="commentCount" property="commentCount" />
<result column="commentScore" property="commentScore" />
<result column="viewProperties" property="viewProperties" />
<result column="goodCommentCount" property="goodCommentCount" />
<result column="commonCommentCount" property="commonCommentCount" />
<result column="medias" property="medias" />
<result column="badCommentCount" property="badCommentCount" />
</resultMap>
<!--List<Product> loadPageData(Page<Product> page, ProductQuery query);-->
<select id="loadPageData" parameterType="ProductQuery" resultMap="productMap">
SELECT
p.*, pt.id ptid,
pt.`name` ptname,
b.id bid,
b. NAME bname,
pe.id peid,
pe.description,
pe.richContent
FROM
t_product p
LEFT JOIN t_product_type pt ON p.product_type_id = pt.id
LEFT JOIN t_brand b ON p.brand_id = b.id
LEFT JOIN t_product_ext pe on p.id = pe.productId
order by id ASC
</select>
<resultMap id="productMap" type="Product">
<!--基本信息管理-->
<id column="id" property="id" />
<result column="createTime" property="createTime" />
<result column="updateTime" property="updateTime" />
<result column="name" property="name" />
<result column="subName" property="subName" />
<result column="code" property="code" />
<result column="product_type_id" property="productTypeId" />
<result column="onSaleTime" property="onSaleTime" />
<result column="offSaleTime" property="offSaleTime" />
<result column="brand_id" property="brandId" />
<result column="state" property="state" />
<result column="maxPrice" property="maxPrice" />
<result column="minPrice" property="minPrice" />
<result column="saleCount" property="saleCount" />
<result column="viewCount" property="viewCount" />
<result column="commentCount" property="commentCount" />
<result column="commentScore" property="commentScore" />
<result column="viewProperties" property="viewProperties" />
<result column="goodCommentCount" property="goodCommentCount" />
<result column="commonCommentCount" property="commonCommentCount" />
<result column="medias" property="medias" />
<result column="badCommentCount" property="badCommentCount" />
<!--品牌和类型-->
<association property="brand" javaType="Brand">
<id column="bid" property="id" />
<result column="bname" property="name" />
</association>
<association property="productType" javaType="ProductType">
<id column="ptid" property="id" />
<result column="ptname" property="name" />
</association>
<association property="productExt" javaType="ProductExt">
<id column="peid" property="id" />
<result column="description" property="description" />
<result column="richContent" property="richContent" />
</association>
</resultMap>
</mapper>
@RestController
@RequestMapping("/product")
public class ProductController {
@Autowired
public IProductService productService;
/**
* 保存和修改公用的
* @param product 传递的实体
* @return Ajaxresult转换结果
*/
@RequestMapping(value="/save",method= RequestMethod.POST)
public AjaxResult save(@RequestBody Product product){
try {
if(product.getId()!=null){
productService.updateById(product);
}else{
productService.insert(product);
}
return AjaxResult.me();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMessage("保存对象失败!"+e.getMessage());
}
}
/**
* 删除对象信息
*/
@RequestMapping(value="/{id}",method=RequestMethod.DELETE)
public AjaxResult delete(@PathVariable("id") Long id){
try {
productService.deleteById(id);
return AjaxResult.me();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.me().setMessage("删除对象失败!"+e.getMessage());
}
}
//获取用户
@RequestMapping(value = "/{id}",method = RequestMethod.GET)
public Product get(@PathVariable("id")Long id){
return productService.selectById(id);
}
/**
* 查看所有的员工信息
*/
@RequestMapping(value = "/list",method = RequestMethod.GET)
public List<Product> list(){
return productService.selectList(null);
}
/**
* 分页查询数据
* @param query 查询对象
* @return PageList 分页对象
*/
@RequestMapping(value = "/json",method = RequestMethod.POST)
public PageList<Product> json(@RequestBody ProductQuery query){
return productService.selectPageList(query);
}
}
4、完成前端代码
①商品类型
拷贝代码到如下位置
<!-- 树状选择器 -->
<template>
<el-popover
ref="popover"
placement="bottom-start"
trigger="click"
@show="onShowPopover"
@hide="onHidePopover">
<el-tree
ref="tree"
class="select-tree"
highlight-current
:style="`min-width: ${treeWidth}`"
:data="data"
:props="props"
:expand-on-click-node="false"
:filter-node-method="filterNode"
:default-expand-all="false"
@node-click="onClickNode">
</el-tree>
<el-input
slot="reference"
ref="input"
v-model="labelModel"
clearable
:style="`width: ${width}px`"
:class="{ 'rotate': showStatus }"
suffix-icon="el-icon-arrow-down"
:placeholder="placeholder">
</el-input>
</el-popover>
</template>
<script>
export default {
name: 'Pagination',
props: {
// 接收绑定参数
value: String,
// 输入框宽度
width: String,
// 选项数据
options: {
type: Array,
required: true,
},
// 输入框占位符
placeholder: {
type: String,
required: false,
default: '请选择',
},
// 树节点配置选项
props: {
type: Object,
required: false,
default: () => ({
parent: 'pid',
value: 'id',
label: 'name',
children: 'children',
}),
},
},
// 设置绑定参数
model: {
prop: 'value',
event: 'selected',
},
computed: {
// 是否为树状结构数据
dataType() {
const jsonStr = JSON.stringify(this.options);
return jsonStr.indexOf(this.props.children) !== -1;
},
// 若非树状结构,则转化为树状结构数据
data() {
return this.dataType ? this.options : this.switchTree();
},
},
watch: {
labelModel(val) {
if (!val) {
this.valueModel = '';
}
this.$refs.tree.filter(val);
},
value(val) {
this.labelModel = this.queryTree(this.data, val);
},
},
data() {
return {
// 树状菜单显示状态
showStatus: false,
// 菜单宽度
treeWidth: 'auto',
// 输入框显示值
labelModel: '',
// 实际请求传值
valueModel: '0',
};
},
created() {
// 检测输入框原有值并显示对应 label
if (this.value) {
this.labelModel = this.queryTree(this.data, this.value);
}
// 获取输入框宽度同步至树状菜单宽度
this.$nextTick(() => {
this.treeWidth = `${(this.width || this.$refs.input.$refs.input.clientWidth) - 24}px`;
});
},
methods: {
// 单击节点
onClickNode(node) {
this.labelModel = node[this.props.label];
this.valueModel = node[this.props.value];
this.onCloseTree();
},
// 偏平数组转化为树状层级结构
switchTree() {
return this.cleanChildren(this.buildTree(this.options, '0'));
},
// 隐藏树状菜单
onCloseTree() {
this.$refs.popover.showPopper = false;
},
// 显示时触发
onShowPopover() {
this.showStatus = true;
this.$refs.tree.filter(false);
},
// 隐藏时触发
onHidePopover() {
this.showStatus = false;
this.$emit('selected', this.valueModel);
},
// 树节点过滤方法
filterNode(query, data) {
if (!query) return true;
return data[this.props.label].indexOf(query) !== -1;
},
// 搜索树状数据中的 ID
queryTree(tree, id) {
let stark = [];
stark = stark.concat(tree);
while (stark.length) {
const temp = stark.shift();
if (temp[this.props.children]) {
stark = stark.concat(temp[this.props.children]);
}
if (temp[this.props.value] === id) {
return temp[this.props.label];
}
}
return '';
},
// 将一维的扁平数组转换为多层级对象
buildTree(data, id = '0') {
const fa = (parentId) => {
const temp = [];
for (let i = 0; i < data.length; i++) {
const n = data[i];
if (n[this.props.parent] === parentId) {
n.children = fa(n.rowGuid);
temp.push(n);
}
}
return temp;
};
return fa(id);
},
// 清除空 children项
cleanChildren(data) {
const fa = (list) => {
list.map((e) => {
if (e.children.length) {
fa(e.children);
} else {
delete e.children;
}
return e;
});
return list;
};
return fa(data);
},
},
};
</script>
<style>
.el-input.el-input--suffix {
cursor: pointer;
overflow: hidden;
}
.el-input.el-input--suffix.rotate .el-input__suffix {
transform: rotate(180deg);
}
.select-tree {
max-height: 350px;
overflow-y: scroll;
}
/* 菜单滚动条 */
.select-tree::-webkit-scrollbar {
z-index: 11;
width: 6px;
}
.select-tree::-webkit-scrollbar-track,
.select-tree::-webkit-scrollbar-corner {
background: #fff;
}
.select-tree::-webkit-scrollbar-thumb {
border-radius: 5px;
width: 6px;
background: #b4bccc;
}
.select-tree::-webkit-scrollbar-track-piece {
background: #fff;
width: 6px;
}
</style>
注:这坨代码很关键
然后在product.vue中引入
使用:
② vue富文本编辑器
安装:npm install vue-quill-editor --save
引入:
使用:
源码和文档: https://pan.baidu.com/s/1PT3FDUAvCm64oxG53lQdNw