font-face是CSS3中的一个模块,他主要是把自己定义的Web字体嵌入到你的网页中,随着@font-face模块的出现,我们在Web的开发中使用字体不怕只能使用Web安全字体,你们当中或许有许多人会不自然的问,这样的东西IE能支持吗?当我告诉大家@font-face这个功能早在IE4就支持了你肯定会感到惊讶。我的Blog就使用了许多这样的自定义Web字体,比如说首页的Logo,Tags以及页面中的手写英文体,很多朋友问我如何使用,能让自己的页面也支持这样的自定义字体,一句话这些都是@font-face实现的,为了能让更多的朋友知道如何使用他,今天我主要把自己的一点学习过程贴上来和大家分享。
首先我们一起来看看@font-face的语法规则:
@font-face { font-family: <YourWebFontName>; src: <source> [<format>][,<source> [<format>]]*; [font-weight: <weight>]; [font-style: <style>]; }
取值说明
兼容浏览器
说到浏览器对@font-face的兼容问题,这里涉及到一个字体format的问题,因为不同的浏览器对字体格式支持是不一致的,这样大家有必要了解一下,各种版本的浏览器支持什么样的字体,前面也简单带到了有关字体的几种格式,下面我就分别说一下这个问题,让大家心里有一个概念:
一、TureTpe(.ttf)格式:
.ttf字体是Windows和Mac的最常见的字体,是一种RAW格式,因此他不为网站优化,支持这种字体的浏览器有【IE9+,Firefox3.5+,Chrome4+,Safari3+,Opera10+,iOS Mobile Safari4.2+】;
二、OpenType(.otf)格式:
.otf字体被认为是一种原始的字体格式,其内置在TureType的基础上,所以也提供了更多的功能,支持这种字体的浏览器有【Firefox3.5+,Chrome4.0+,Safari3.1+,Opera10.0+,iOS Mobile Safari4.2+】;
三、Web Open Font Format(.woff)格式:
.woff字体是Web字体中最佳格式,他是一个开放的TrueType/OpenType的压缩版本,同时也支持元数据包的分离,支持这种字体的浏览器有【IE9+,Firefox3.5+,Chrome6+,Safari3.6+,Opera11.1+】;
四、Embedded Open Type(.eot)格式:
.eot字体是IE专用字体,可以从TrueType创建此格式字体,支持这种字体的浏览器有【IE4+】;
五、SVG(.svg)格式:
.svg字体是基于SVG字体渲染的一种格式,支持这种字体的浏览器有【Chrome4+,Safari3.1+,Opera10.0+,iOS Mobile Safari3.2+】。
这就意味着在@font-face中我们至少需要.woff,.eot两种格式字体,甚至还需要.svg等字体达到更多种浏览版本的支持。
为了使@font-face达到更多的浏览器支持,Paul Irish写了一个独特的@font-face语法叫Bulletproof @font-face:
@font-face { font-family: 'YourWebFontName'; src: url('YourWebFontName.eot?') format('eot');/*IE*/ src:url('YourWebFontName.woff') format('woff'), url('YourWebFontName.ttf') format('truetype');/*non-IE*/ }
但为了让各多的浏览器支持,你也可以写成:
@font-face { font-family: 'YourWebFontName'; src: url('YourWebFontName.eot'); /* IE9 Compat Modes */ src: url('YourWebFontName.eot?#iefix') format('embedded-opentype'), /* IE6-IE8 */ url('YourWebFontName.woff') format('woff'), /* Modern Browsers */ url('YourWebFontName.ttf') format('truetype'), /* Safari, Android, iOS */ url('YourWebFontName.svg#YourWebFontName') format('svg'); /* Legacy iOS */ }
说了这么多空洞的理论知识,大家一定有点心痒痒了,那么我们先来看看W3CPLUS首页中导航部分的兰色字体是如何实现的,假如我们有一个这样的DOM标签,需要应用自定义字体:
HTML Code:
<h2 class="neuesDemo">Neues Bauen Demo</h2>
通过@font-face来定义自己的Web Font:
@font-face { font-family: 'NeuesBauenDemo'; src: url('../fonts/neues_bauen_demo-webfont.eot'); src: url('../fonts/neues_bauen_demo-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/neues_bauen_demo-webfont.woff') format('woff'), url('../fonts/neues_bauen_demo-webfont.ttf') format('truetype'), url('../fonts/neues_bauen_demo-webfont.svg#NeuesBauenDemo') format('svg'); font-weight: normal; font-style: normal; }
我在这里采用的是相对路径,当然大家也可以使用绝路径。到这里我们就需要把定义好的字体应用到我们实际页面中去:
h2.neuesDemo { font-family: 'NeuesBauenDemo' }
效果:
看到上面的效果,我想大家会感到@font-face很神奇,同时也想争着做做看,可是一动手才发现,特殊字体我要怎样才能得到,那些.eot,.woff,.ttf,.svg这些字体格式又怎么获取呢?有些朋友可能就不知道如何运手了,那么我们就带着这些问题来一个全程完成的实例吧:
一、获取特殊字体:
我们拿下面这种single Malta字体来说吧:
要得到single Malta字体,不外乎两种途径,其一找到付费网站购买字体,其二就是到免费网站DownLoad字体。当然要给钱的这种傻事我想大家都不会做的,那我们就得到免费的地方下载,在哪有呢?我平时都是到Google Web Fonts和Dafont.com寻找自己需要的字体,当然网上也还有别的下载字体的地方,这个Demo使用的是Dafont.com的Single Malta字体,这样就可以到这里下载Single Malta:
Single Malta下载下来后,需要把它解压缩出来:
二、获取@font-face所需字体格式:
特殊字体已经在你的电脑中了,现在我们需要想办法获得@font-face所需的.eot,.woff,.ttf,.svg字体格式。要获取这些字体格式,我们同样是需要第三方工具或者软件来实现,下面我给大家推荐一款我常用的一个工具fontsquirrel,别的先不多说,首跟我点这里进入到下面这个界面吧。
如果你进入页面没有看到上图,你可以直接点击导航:
如果你看到了上面的界面,那就好办了,我们来看如何应用这个工具生成@font-face需要的各种字体,先把我们刚才下载的字体上传上去:
上传后按下图所示操作:
现在从Font Squirrel下载下来的文件已经保存在你本地的电脑上了,接着只要对他进行解压缩,你就能看到文件列表如下所示:
大家可以看到,解压缩出来的文件格式,里面除了@font-face所需要的字体格式外,还带有一个DEMO文件,如果你不清楚的也可以参考下载下来的DEMO文件,我在这里不对DEMO说明问题,我主要是给大家介绍如何把下载下来的文件有价值的运用到我们的项目中。
例如在自己的本地创建了一个fontface项目:
为了让项目结构更清晰,我们在项目中单独创建一个fonts目录,用来放置解压缩出来@font-face所需的字体格式:
现在@font-face所需字体已经加载到本地项目,现在本地项目中的style.css中附上我们需要的@font-face样式
@font-face { font-family: 'SingleMaltaRegular'; src: url('../fonts/singlemalta-webfont.eot'); src: url('../fonts/singlemalta-webfont.eot?#iefix') format('embedded-opentype'), url('../fonts/singlemalta-webfont.woff') format('woff'), url('../fonts/singlemalta-webfont.ttf') format('truetype'), url('../fonts/singlemalta-webfont.svg#SingleMaltaRegular') format('svg'); font-weight: normal; font-style: normal; }
到这里为止,我们已经通过@font-face自定义好所需的SingleMalta字体,离最后效果只差一步了,就是把自己定义的字体应用到你的Web中的DOM元素上:
h2.singleMalta { font-family: 'SingleMaltaRegular' }
效果:
看到上面的效果,那大家就知道我们实现成功了。那么关于@font-face帮你打造特殊效果的字体,到这里基本上就完成了,我在这里需要提醒使用者:
1、如果你的项目中是英文网站,而且项目中的Logo,Tags等应用到较多的这种特殊字体效果,我建议你不要使用图片效果,而使用@font-face,但是你是中文网站,我觉得还是使用图片比较合适,因为加载英文字体和图片没有多大区别,但是你加载中文字体,那就不一样了,因为中文字体太大了,这样会影响到项目的某些性能的优化;
2、致命的错误,你在@font-face中定义时,文件路径没有载对;
3、你只定义了@font-face,但并没有应用到你的项目中的DOM元素上;
以上几点都是在平时制作中常出现的问题,希望大家能小意一些,另外我们没有办法在购买所有字体,就算你实力雄厚,那也没有办法在一台服务器主机上放置你所有项目需要的字体。因此我给大家提供几个免费字体下载的网址:Webfonts,Typekit,Kernest,Google Web Fonts,Kernest,Dafont,Niec Web Type,不然你点这里将有更多的免费字体。前面几个链接是帮助你获取一些优美的怪异的特殊字体,但下面这个工具作用更是无穷的大,他能帮你生成@font-face所需要的各种字体,这工具就是Font Squirrel。
最后在提醒一下,使用@font-face别的可以忘了,但Font Squirrel千万不能忘,因为他能帮你生成@font-face所需的各种字体格式。
到此关于@font-face就介绍完了,不知道大家喜欢不喜欢,如果喜欢的话赶快动手实践一下,有Blog的可以马上运用上去,也可以炫一下。
学习从来不是一个人的事情,要有个相互监督的伙伴,想要学习或交流前端问题的小伙伴可以私信“学习”小明加群获取2019web前端最新入门资料,一起学习,一起成长!
??商城系统中的商品信息肯定避免不了SPU和SKU这两个概念,本节就给大家详细介绍下这块的内容
SPU=Standard Product Unit (标准化产品单元)
SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。通俗点讲,属性值、特性相同的商品就可以称为一个SPU。
SKU=stock keeping unit(库存量单位)
SKU即库存进出计量的单位, 可以是以件、盒、托盘等为单位。
SKU是物理上不可分割的最小存货单元。在使用时要根据不同业态,不同管理模式来处理。在服装、鞋类商品中使用最多最普遍。
举个例子:
购买手机的时候,你可以选择华为Mate40系列手机,Mate40系列手机的生产制造商是华为,品牌是华为,手机分类也是华为,不过Mate40系列手机有多款,比如 Mate40 、Mate40 Pro 、 Mate40 Pro +,每款手机的架构也不一样,颜色也不一定一样,那么这个例子中哪些是Spu哪些是Sku呢?
Spu:
手机系列:Mate40系列
厂家:华为
品牌:华为
分类:手机
Sku:
价格
颜色
网络格式
spu:
CREATE TABLE `spu` (
`id` varchar(60) NOT NULL COMMENT '主键',
`name` varchar(100) DEFAULT NULL COMMENT 'SPU名',
`intro` varchar(200) DEFAULT NULL COMMENT '简介',
`brand_id` int(11) DEFAULT NULL COMMENT '品牌ID',
`category_one_id` int(20) DEFAULT NULL COMMENT '一级分类',
`category_two_id` int(10) DEFAULT NULL COMMENT '二级分类',
`category_three_id` int(10) DEFAULT NULL COMMENT '三级分类',
`images` varchar(1000) DEFAULT NULL COMMENT '图片列表',
`after_sales_service` varchar(50) DEFAULT NULL COMMENT '售后服务',
`content` longtext COMMENT '介绍',
`attribute_list` varchar(3000) DEFAULT NULL COMMENT '规格列表',
`is_marketable` int(1) DEFAULT '0' COMMENT '是否上架,0已下架,1已上架',
`is_delete` int(1) DEFAULT '0' COMMENT '是否删除,0:未删除,1:已删除',
`status` int(1) DEFAULT '0' COMMENT '审核状态,0:未审核,1:已审核,2:审核不通过',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
sku:
CREATE TABLE `sku` (
`id` varchar(60) NOT NULL COMMENT '商品id',
`name` varchar(200) NOT NULL COMMENT 'SKU名称',
`price` int(20) NOT NULL DEFAULT '1' COMMENT '价格(分)',
`num` int(10) DEFAULT '100' COMMENT '库存数量',
`image` varchar(200) DEFAULT NULL COMMENT '商品图片',
`images` varchar(2000) DEFAULT NULL COMMENT '商品图片列表',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`spu_id` varchar(60) DEFAULT NULL COMMENT 'SPUID',
`category_id` int(10) DEFAULT NULL COMMENT '类目ID',
`category_name` varchar(200) DEFAULT NULL COMMENT '类目名称',
`brand_id` int(11) DEFAULT NULL COMMENT '品牌id',
`brand_name` varchar(100) DEFAULT NULL COMMENT '品牌名称',
`sku_attribute` varchar(200) DEFAULT NULL COMMENT '规格',
`status` int(1) DEFAULT '1' COMMENT '商品状态 1-正常,2-下架,3-删除',
PRIMARY KEY (`id`),
KEY `cid` (`category_id`),
KEY `status` (`status`),
KEY `updated` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';
商品发布流程如下:
1)分类选择
发布商品前,需要先选择发布商品所属分类,分类严格定义为3级分类。
分类表:
CREATE TABLE `category` (
`id` int(20) NOT NULL AUTO_INCREMENT COMMENT '分类ID',
`name` varchar(50) DEFAULT NULL COMMENT '分类名称',
`sort` int(11) DEFAULT NULL COMMENT '排序',
`parent_id` int(20) DEFAULT NULL COMMENT '上级ID',
PRIMARY KEY (`id`),
KEY `parent_id` (`parent_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11182 DEFAULT CHARSET=utf8 COMMENT='商品类目';
2)选择品牌
分类选择完成后,需要加载品牌,品牌加载并非一次性加载完成,而是根据选择的分类进行加载。
分类品牌关系表:
CREATE TABLE `category_brand` (
`category_id` int(11) NOT NULL COMMENT '分类ID',
`brand_id` int(11) NOT NULL COMMENT '品牌ID',
PRIMARY KEY (`brand_id`,`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
品牌表:
CREATE TABLE `brand` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '品牌id',
`name` varchar(100) NOT NULL COMMENT '品牌名称',
`image` varchar(1000) DEFAULT '' COMMENT '品牌图片地址',
`initial` varchar(1) DEFAULT '' COMMENT '品牌的首字母',
`sort` int(11) DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='品牌表';
3)属性加载
当选择分类后,加载分类对应的属性。
分类属性表:
CREATE TABLE `category_attr` (
`category_id` int(11) NOT NULL,
`attr_id` int(11) NOT NULL COMMENT '属性分类表',
PRIMARY KEY (`category_id`,`attr_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
属性表:
CREATE TABLE `sku_attribute` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
`name` varchar(50) DEFAULT NULL COMMENT '属性名称',
`options` varchar(2000) DEFAULT NULL COMMENT '属性选项',
`sort` int(11) DEFAULT NULL COMMENT '排序',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;
对应的Bean我们已经提前写好,如下
package com.bobo.vip.mall.goods.model;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/*****
* @Author: 波波
* @Description: 云商城
****/
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value="category")
public class Category implements Serializable {
@TableId(type=IdType.AUTO)
private Integer id;
private String name;
private Integer sort;
private Integer parentId;
}
package com.bobo.vip.mall.goods.model;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/*****
* @Author: 波波
* @Description: 云商城
****/
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value="category_attr")
public class CategoryAttr {
@TableField
private Integer categoryId;
@TableField
private Integer attrId;
}
/*****
* @Author: 波波
* @Description: 云商城
****/
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value="category_brand")
public class CategoryBrand {
@TableField
private Integer categoryId;
@TableField
private Integer brandId;
}
/*****
* @Author: 波波
* @Description: 云商城
****/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
// Spu
private Spu spu;
// Sku
private List<Sku> skus;
}
1234567891011121314
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value="sku")
public class Sku {
@TableId(type=IdType.ASSIGN_ID)
private String id;
private String name;
private Integer price;
private Integer num;
private String image;
private String images;
private Date createTime;
private Date updateTime;
private String spuId;
private Integer categoryId;
private String categoryName;
private Integer brandId;
private String brandName;
private String skuAttribute;
private Integer status;
}
/*****
* @Author: 波波
* @Description: 云商城
****/
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value="sku_attribute")
public class SkuAttribute implements Serializable {
@TableId(type=IdType.AUTO)
private Integer id;
private String name;
private String options;
private Integer sort;
//对应分类
@TableField(exist=false)
private List<Category> categories;
}
12345678910111213141516171819202122
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value="spu")
public class Spu {
@TableId(type=IdType.ASSIGN_ID)
private String id;
private String name;
private String intro;
private Integer brandId;
private Integer categoryOneId;
private Integer categoryTwoId;
private Integer categoryThreeId;
private String images;
private String afterSalesService;
private String content;
private String attributeList;
private Integer isMarketable;
private Integer isDelete;
private Integer status;
}
分类功能需要实现按照父ID查询,最开始初始化加载的是顶级父类,parent_id=0,后面每次点击的时候都根据传入的id查询子分类。
1)Mapper
创建com.bobo.vip.mall.goods.mapper.CategoryMapper,代码如下:
public interface CategoryMapper extends BaseMapper<Category> {
}
12
2)Service
接口:com.bobo.vip.mall.goods.service.CategoryService代码如下:
public interface CategoryService extends IService<Category> {
/**
* 根据父ID查询子分类
* @param pid
* @return
*/
List<Category> queryByParentId(Integer pid);
}
123456789
实现类:com.bobo.vip.mall.goods.service.impl.CategoryServiceImpl代码如下:
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper,Category> implements CategoryService {
@Autowired
private CategoryMapper categoryMapper;
/***
* 根据父ID查询子分类
* @param pid
* @return
*/
@Override
public List<Category> queryByParentId(Integer pid) {
//条件封装
QueryWrapper<Category> queryWrapper=new QueryWrapper<Category>();
queryWrapper.eq("parent_id",pid);
return categoryMapper.selectList(queryWrapper);
}
}
12345678910111213141516171819
3)Controller
创建com.bobo.vip.mall.goods.controller.CategoryController代码如下;
@RestController
@RequestMapping(value="/category")
@CrossOrigin
public class CategoryController {
@Autowired
private CategoryService categoryService;
/****
* 根据父ID查询子分类
*/
@GetMapping(value="/parent/{pid}")
public RespResult<List<Category>> list(@PathVariable(value="pid")Integer pid){
List<Category> categories=categoryService.queryByParentId(pid);
return RespResult.ok(categories);
}
}
1234567891011121314151617
品牌需要根据分类进行加载,当用户选择第3级分类的时候,加载品牌,品牌数据需要经过category_brand表关联查询。
我们可以按照如下步骤实现:
1、查询category_brand中指定分类对应的品牌ID集合
2、从brand查出品牌集合
1)Mapper
修改com.bobo.vip.mall.goods.mapper.BrandMapper,添加根据分类ID查询品牌ID集合:
//根据分类ID查询品牌集合
@Select("select brand_id from category_brand where category_id=#{id}")
List<Integer> queryBrandIds(Integer id);
123
2)Service
接口:com.bobo.vip.mall.goods.service.BrandService中添加根据分类ID查询品牌集合方法
//根据分类ID查询品牌
List<Brand> queryByCategoryId(Integer id);
12
实现类:com.bobo.vip.mall.goods.service.impl.BrandServiceImpl
/***
* 根据分类ID查询品牌
* @param id
* @return
*/
@Override
public List<Brand> queryByCategoryId(Integer id) {
//查询分类ID对应的品牌集合
List<Integer> brandIds=brandMapper.queryBrandIds(id);
//根据品牌ID集合查询品牌信息
List<Brand> brands=brandMapper.selectBatchIds(brandIds);
return brands;
}
12345678910111213
3)Controller
修改com.bobo.vip.mall.goods.controller.BrandController添加根据分类ID查询品牌集合
/****
* 根据分类ID查询品牌
*/
@GetMapping(value="/category/{id}")
public RespResult<List<Brand>> categoryBrands(@PathVariable(value="id")Integer id){
List<Brand> brands=brandService.queryByCategoryId(id);
return RespResult.ok(brands);
}
12345678
属性也称为规格,属性也需要根据分类查询,我们可以按照如下思路实现:
1、先从category_attr根据分类ID查询出当前分类拥有的属性ID集合
2、从sku_attribute中查询属性集合
1)Mapper
创建com.bobo.vip.mall.goods.mapper.SkuAttributeMapper实现根据分类ID查询属性信息。
/***
* 根据分类ID查询属性集合
* @param id
* @return
*/
@Select("SELECT * FROM sku_attribute WHERE id IN(SELECT attr_id FROM category_attr WHERE category_id=#{id})")
List<SkuAttribute> queryByCategoryId(Integer id);
1234567
2)Service
接口:com.bobo.vip.mall.goods.service.SkuAttributeService添加根据分类ID查询属性集合方法
//根据分类ID查询属性集合
List<SkuAttribute> queryList(Integer id);
12
实现类:com.bobo.vip.mall.goods.service.impl.SkuAttributeServiceImpl添加如下实现方法
/***
* 根据分类ID查询属性集合
* @param id
* @return
*/
@Override
public List<SkuAttribute> queryList(Integer id) {
return skuAttributeMapper.queryByCategoryId(id);
}
123456789
3)Controller
创建com.bobo.vip.mall.goods.controller.SkuAttributeController,添加如下方法
/***
* 根据分类ID查询
*/
@GetMapping(value="/category/{id}")
public RespResult<SkuAttribute> categoryAttributeList(@PathVariable(value="id")Integer id){
//根据分类ID查询属性参数
List<SkuAttribute> skuAttributes=skuAttributeService.queryList(id);
return RespResult.ok(skuAttributes);
}
123456789
商品发布,如上图,我们可以发现发布的商品信息包含Sku和Spu,因此我们应该在后端能有一个对象同时能接到Spu和多个Sku,方法有很多种,我们可以直接在Spu中写一个List<Sku>,但这种方法不推荐,按照对象设计原则,对一个对象进行扩展时,尽量避免对原始对象造成改变,因此我们可以使用复合类,可以创建一个Prodcut类,该类中有Spu也有List<Sku>,代码如下:
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
// Spu
private Spu spu;
// Sku
private List<Sku> skus;
}
123456789
添加商品的时候,我们需要保存Spu,同时需要添加多个Sku。我们可以在华为商城中看看真实电商中Sku名字特征,每次点击不同属性的时候,前部分名字一样,只是将名字中的规格替换了,也就是说Sku的名字其实是组合成的,一部分是Spu的一部分是Sku的,可以进行组合。
1)名字分析
添加商品的时候,会将商品的属性传入后台,格式如下,如果把规格名字添加到名字中,那就是华为商城中的效果了,我们可以这么做,把属性解析成Map,然后每个属性值添加到商品名字中即可。
{"适合人群":"有一定java基础的人","书籍分类":"软件编程"}
2)实现代码
Mapper
com.bobo.vip.mall.goods.mapper.SpuMapper代码如下:
public interface SpuMapper extends BaseMapper<Spu> {
}
12
com.bobo.vip.mall.goods.mapper.SkuMapper代码如下:
public interface SkuMapper extends BaseMapper<Sku> {
}
12
Service
com.bobo.vip.mall.goods.service.SpuService中添加产品方法如下
public interface SpuService extends IService<Spu> {
//保存商品
void saveProduct(Product product);
}
1234
com.bobo.vip.mall.goods.service.impl.SpuServiceImpl中添加产品方法如下:
@Service
public class SpuServiceImpl extends ServiceImpl<SpuMapper,Spu> implements SpuService {
@Autowired
private SkuMapper skuMapper;
@Autowired
private SpuMapper spuMapper;
@Autowired
private CategoryMapper categoryMapper;
@Autowired
private BrandMapper brandMapper;
// 保存商品
@Override
public void saveProduct(Product product) {
//Spu
Spu spu=product.getSpu();
//上架
spu.setIsMarketable(1);
//未删除
spu.setIsDelete(0);
//状态
spu.setStatus(1);
//添加
spuMapper.insert(spu);
//查询三级分类
Category category=categoryMapper.selectById(spu.getCategoryThreeId());
//查询品牌
Brand brand=brandMapper.selectById(spu.getBrandId());
//当前时间
Date now=new Date();
//新增Sku集合
for (Sku sku : product.getSkus()) {
//设置名字
String skuName=spu.getName();
Map<String,String> attrMap=JSON.parseObject(sku.getSkuAttribute(), Map.class);
for (Map.Entry<String, String> entry : attrMap.entrySet()) {
skuName+=" "+entry.getValue();
}
sku.setName(skuName);
//设置图片
sku.setImages(spu.getImages());
//设置状态
sku.setStatus(1);
//设置类目ID
sku.setCategoryId(spu.getCategoryThreeId());
//设置类目名称
sku.setCategoryName(category.getName());
//设置品牌ID
sku.setBrandId(brand.getId());
//设置品牌名称
sku.setBrandName(brand.getName());
//设置Spuid
sku.setSpuId(spu.getId());
//时间
sku.setCreateTime(now);
sku.setUpdateTime(now);
//增加
skuMapper.insert(sku);
}
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768
Controller
创建com.bobo.vip.mall.goods.controller.SpuController,添加产品代码如下:
@Autowired
private SpuService spuService;
/***
* 保存
*/
@PostMapping(value="/save")
public RespResult save(@RequestBody Product product){
//保存
spuService.saveProduct(product);
return RespResult.ok();
}
产品修改其实和产品添加几乎一致,只需要做小改动即可,实现步骤如下:
1、如果Spu的id值不为空,说明是修改操作
2、如果是修改操作,先删除之前对应的Sku集合
3、其他流程和添加商品一致
修改com.bobo.vip.mall.goods.service.impl.SpuServiceImpl的save方法,代码如下:
在这里插入图片描述
源码如下:
@Override
public void saveProduct(Product product) {
//Spu
Spu spu=product.getSpu();
//如果ID为空,则增加
if(StringUtils.isEmpty(spu.getId())){
//上架
spu.setIsMarketable(1);
//未删除
spu.setIsDelete(0);
//状态
spu.setStatus(1);
//添加
spuMapper.insert(spu);
}else{
//ID 不为空,则修改
spuMapper.updateById(spu);
//删除之前的Sku记录
skuMapper.delete(new QueryWrapper<Sku>().eq("spu_id",spu.getId()));
}
//查询三级分类
Category category=categoryMapper.selectById(spu.getCategoryThreeId());
//查询品牌
Brand brand=brandMapper.selectById(spu.getBrandId());
//当前时间
Date now=new Date();
//新增Sku集合
for (Sku sku : product.getSkus()) {
//设置名字
String skuName=spu.getName();
Map<String,String> attrMap=JSON.parseObject(sku.getSkuAttribute(), Map.class);
for (Map.Entry<String, String> entry : attrMap.entrySet()) {
skuName+=" "+entry.getValue();
}
sku.setName(skuName);
//设置图片
sku.setImages(spu.getImages());
//设置状态
sku.setStatus(1);
//设置类目ID
sku.setCategoryId(spu.getCategoryThreeId());
//设置类目名称
sku.setCategoryName(category.getName());
//设置品牌ID
sku.setBrandId(brand.getId());
//设置品牌名称
sku.setBrandName(brand.getName());
//设置Spuid
sku.setSpuId(spu.getId());
//时间
sku.setCreateTime(now);
sku.setUpdateTime(now);
//增加
skuMapper.insert(sku);
}
}
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
我们可以发现个问题,刚才写的很多增删改查代码都比较简单,比较枯燥,重复写一些类的创建、单表增删改查非常类,而创建对象和单标操作的代码,在开发中几乎占用了开发时间的80%,如果能够用工具生成就可以大大节省我们开发成本了。
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
学习网址 https://baomidou.com/guide/generator.html
1)引入依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>org.apache.velocity</groupId>
<artifactId>velocity-engine-core</artifactId>
<version>2.2</version>
</dependency>
1234567891011
2)代码生成
public static void main(String[] args) {
// 代码生成器
AutoGenerator mpg=new AutoGenerator();
// 全局配置
GlobalConfig gc=new GlobalConfig();
String projectPath=System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java"); // 文件输出路径
gc.setAuthor("bobo"); //作者
gc.setOpen(false); //生成之后是否打开目录
gc.setIdType(IdType.NONE); //主键策略
gc.setServiceName("%sService"); //名字设置 %s是占位符,可以理解成类的名字
mpg.setGlobalConfig(gc);
// 数据源配置
DataSourceConfig dsc=new DataSourceConfig();
dsc.setUrl("jdbc:mysql://192.168.100.140:3306/shop_goods?useUnicode=true&useSSL=false&characterEncoding=utf8");
dsc.setDriverName("com.mysql.jdbc.Driver");
dsc.setUsername("root");
dsc.setPassword("123456");
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc=new PackageConfig();
pc.setModuleName("mall-goods");
pc.setParent("com.bobo.code");
mpg.setPackageInfo(pc);
// 策略配置
StrategyConfig strategy=new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel); //驼峰命名
strategy.setColumnNaming(NamingStrategy.underline_to_camel); //驼峰命名
strategy.setEntityLombokModel(true); //是否使用Lombok
strategy.setRestControllerStyle(true); //是否生成RestController
// 写于父类中的公共字段
strategy.setSuperEntityColumns("id"); //公共字段定义
strategy.setControllerMappingHyphenStyle(true); //驼峰转连字符
strategy.setTablePrefix(pc.getModuleName() + "_"); //表前缀
mpg.setStrategy(strategy);
mpg.execute();
}
效果
有红色的提示是因为没有引入依赖,我们可以把生成的相关内容拷贝到合适的项目位置即可。
Vue.js 3.x+Express全栈开发:从0到1打造商城项目》
1
本书内容
《Vue.js 3.x+Express全栈开发 : 从0到1打造商城项目》是一本详尽的全栈开发教程,旨在通过Vue.js和Express框架引导读者从零开始构建一个完整的电商项目。内容覆盖电商项目的基本结构,以及Vue.js和Express的核心概念与架构;深入讲解Vue.js开发生态中的关键模块,包括网络请求、UI组件、路由管理和状态管理等;探讨Express框架的常用组件,如处理加密数据的中间件和与MySQL数据库交互的插件;最后指导读者打造一个完整的电商项目。在用户端,实现注册登录、商品浏览、购物车等功能;在服务端,完成用户验证、商品维护、订单处理等任务;在后台管理端,进行商品信息、订单数据等的管理与统计分析。通过阅读《Vue.js 3.x+Express全栈开发 : 从0到1打造商城项目》,读者能够掌握Vue.js和Express全栈开发技术,并独立完成电商项目的搭建与开发。
《Vue.js 3.x+Express全栈开发 : 从0到1打造商城项目》还提供了完整的项目源码、代码导读手册以及长达30小时的教学视频,可大幅提升学习效率。
2
本书作者
张益珲,美国亚利桑那州立大学计算机工程技术硕士,架构师,从业近10年,多年大前端开发经验,曾就职于知名上市公司,主导开发过多款商业级应用程序,对移动跨平台开发、前端开发,以及Vue.js 、React、Flutter、小程序与iOS开发都拥有丰富经验。开源中国特邀技术专家,发表相关技术博文400余篇,访问量超过100万次。出版技术图书《循序渐进Vue.js 3.x前端开发实战》《微信小程序与云开发从入门到实践》《Swift 5从零到精通iOS开发训练营》等多部。
3
本书读者
《Vue.js 3.x+Express全栈开发 : 从0到1打造商城项目》采用实际商业项目作为教学案例,融入了多种前端框架和新技术,非常适合缺乏项目经验的学生和对全栈开发感兴趣的开发者阅读,也适合作为培训机构和大中专院校相关专业的实践课教学用书。
4
本书目录
第1章 项目概览与环境准备 1
1.1 项目概览 1
1.1.1 电商项目的功能构成 2
1.1.2 前端框架Vue.js及其周边工具 3
1.1.3 熟悉Node.js与Express 4
1.1.4 从JavaScript到TypeScript 5
1.2 脚手架工具的应用 6
1.2.1 安装Node.js环境 6
1.2.2 使用Vue.js脚手架工具Vite 7
1.2.3 使用Express项目生成工具 9
1.2.4 使用Visual Studio Code编程工具 11
1.3 HelloWorld工程解析 13
1.3.1 Vue.js工程解析 13
1.3.2 Express工程解析 16
1.4 小结与上机练习 22
第2章 前端基础模块及应用 24
2.1 axios与vue-axios网络请求模块的应用 25
2.1.1 尝试发起一个HTTP请求 25
2.1.2 axios网络模块的更多用法 27
2.2 Element Plus页面UI组件模块的应用 30
2.2.1 加载Element Plus模块 30
2.2.2 基础UI组件 32
2.2.3 典型的表单类组件 34
2.2.4 典型的数据展示类组件 37
2.2.5 常用的导航组件 40
2.2.6 常用的用户反馈类组件 43
2.3 Vue Router路由模块的应用 46
2.3.1 Vue Router模块的使用 46
2.3.2 动态路由与参数匹配 48
2.3.3 路由的嵌套和命名 51
2.3.4 路由中的导航守卫 54
2.4 Pinia状态管理模块的应用 56
2.4.1 尝试使用Pinia 56
2.4.2 Pinia中的几个核心概念 59
2.5 小结与上机练习 60
第3章 后端服务基础模块及应用 69
3.1 文件上传服务 70
3.1.1 图片上传服务示例 70
3.1.2 Multer中间件的更多用法 74
3.2 在Express中使用MySQL数据库 76
3.2.1 MySQL数据库的安装和简单使用 76
3.2.2 在Express中调用MySQL的相关功能 79
3.3 使用JSON Web Token实现身份授权和验证 82
3.3.1 JSON Web Token简介 82
3.3.2 在Express中使用JWT 83
3.4 使用bcrypt加密模块实现商城安全 86
3.5 小结与上机练习 89
第4章 开发用户登录和注册模块 96
4.1 实现服务端的登录和注册模块 96
4.1.1 用户数据表的定义 97
4.1.2 封装数据库工具类与实现登录和注册接口 98
4.2 实现用户端的登录和注册功能 107
4.2.1 搭建用户端工程 108
4.2.2 开发用户端登录和注册页面 110
4.2.3 开发用户端账户数据逻辑 113
4.2.4 开发用户端登录和注册接口逻辑 115
4.3 实现后台管理端的登录和注册功能 119
4.4 小结与上机练习 121
第5章 开发营销推广模块 124
5.1 实现服务端的运营推广模块 124
5.1.1 定义运营位表结构和接口文档 125
5.1.2 实现运营位图片上传接口 131
5.1.3 实现用户鉴权中间件 133
5.1.4 实现运营位业务接口 136
5.2 实现后台管理端的运营位管理模块 138
5.2.1 搭建后台管理系统首页 138
5.2.2 实现创建运营位组件 141
5.2.3 实现运营位管理模块 146
5.3 实现用户端的运营位模块 149
5.4 小结与上机练习 152
第6章 开发商品列表与详情模块 154
6.1 开发服务端的商品相关模块 154
6.1.1 商品类别表的定义与接口实现 155
6.1.2 商品表与相关接口的实现 158
6.2 实现后台管理端的商品管理模块 167
6.2.1 实现类别管理功能 167
6.2.2 实现商品编辑模块 172
6.2.3 实现商品管理模块 180
6.3 实现用户端的商品模块 183
6.3.1 实现用户端首页商品推荐模块 184
6.3.2 实现用户端的商品详情页 189
6.4 小结与上机练习 193
第7章 开发购物车与订单模块 194
7.1 实现服务端的购物车与订单模块 194
7.1.1 购物车表的定义与功能接口的实现 195
7.1.2 订单表的定义与接口分析 200
7.1.3 实现订单模块后端接口 202
7.2 实现用户端的购物车与订单模块 207
7.2.1 实现购物车功能 207
7.2.2 实现订单模块 212
7.3 实现后台管理端的订单管理模块 217
7.4 小结与上机练习 218
第8章 开发搜索与评价模块 222
8.1 实现服务端的搜索与评价模块 222
8.1.1 实现商品搜索接口 223
8.1.2 评价数据结构与接口定义 224
8.1.3 实现评价相关接口 226
8.2 实现用户端的搜索与评价模块 229
8.2.1 实现搜索功能 230
8.2.2 实现创建商品评价功能 233
8.2.3 实现商品评价展示功能 237
8.3 实现后台管理端的评价模块 239
8.4 小结与上机练习 242
第9章 数据统计模块与项目总结 243
9.1 实现电商后台数据统计模块 243
9.1.1 数据统计功能的后端接口定义 244
9.1.2 数据统计功能的后端服务接口实现 245
9.1.3 后台管理端的数据图表绘制 250
9.2 项目总结 254
9.3 小结与上机练习 256
5
编辑推荐
《Vue.js 3.x+Express全栈开发:从0到1打造商城项目》是一本实战型教程,专注于使用最新的Vue.js 3.x和Express框架来构建一个完整的电子商务平台。以下是您可能需要这本书的原因:
1全面而深入:《Vue.js 3.x+Express全栈开发:从0到1打造商城项目》首先介绍了Vue和Express的基本概念与框架结构,如Vue的组件化开发、数据绑定以及Express的路由处理和中间件使用等,为您打下坚实的基础。
2生态资源介绍:书中详细讲述了Vue和Express生态系统中的核心插件,让您对UI搭建、网络请求、路由管理、数据存储与安全等方面有全面的了解。
实战项目经验:通过引导您搭建一个完整的电商项目,包括前端用户功能和后端API服务,帮助您获得宝贵的实战经验。
3功能完整:从用户注册登录到商品展示、购物车以及后台的商品和订单管理,这本书将指导您一步步实现一个功能完备的电商平台。
4学习资源丰富:《Vue.js 3.x+Express全栈开发:从0到1打造商城项目》提供了完整的项目源代码、导读手册和配套视频教程,极大地便利了您的学习和实践,并加速理解过程。
5适用读者广泛:无论是正在寻求项目经验的开发人员,还是希望通过实践学习的在校学生,抑或是用作高校和培训机构的实践课教材,《Vue.js 3.x+Express全栈开发:从0到1打造商城项目》都是一个极佳的选择。
6这本书将帮助您掌握使用Vue和Express进行全栈开发的能力,更重要的是,在您完成阅读和实践后,能够独立负责电商项目的搭建和开发。
把握机遇,深化知识,提升技能。相信《Vue.js 3.x+Express全栈开发:从0到1打造商城项目》将是您技术成长道路上的一块垫脚石。
本文摘自《Vue.js 3.x+Express全栈开发:从0到1打造商城项目》,获出版社和作者授权发布。
*请认真填写需求信息,我们会在24小时内与您取得联系。