商品详情页-数据显示
运用Freemarker技术来实现商品详细页的静态化。通过地址栏输入某地址,如下形式
http://localhost:9101/gen_item.do?goodsId=149187842867979
能在本地电脑某目录生成商品详细页,页面的名称为商品id.html
工程搭建
服务接口层
- 创建pinyougou-page-interface工程
- 创建com.pinyougou.page.service包
- 包下创建接口ItemPageService
package com.pinyougou.page.service;
/**
* Title: ItemPageService
* Description: 生成页面
* @author: 张楚楚
* @date 2019年2月28日下午3:01:38
*/
public interface ItemPageService {
/**
* 生成商品详细页
* @param goodsId
* @return
*/
public boolean genItemHtml(Long goodsId);
}
服务实现层
- 创建war工程pinyougou-page-service
- pom.xml引入依赖 参见其它服务工程, 另外添加freemarker依赖
<!-- freemarker -->
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
- 添加web.xml 参见其它服务工程
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<!-- 加载spring容器 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath*:spring/applicationContext*.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
</web-app>
- spring配置文件 参见其它服务工程 ,另外配置:
这是spring提供的对freemarker支持的配置文件,属性分别为模板目录和字符集
在WEB-INF下创建ftl目录
<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
<property name="templateLoaderPath" value="/WEB-INF/ftl/" />
<property name="defaultEncoding" value="UTF-8" />
</bean>
另网页生成目录也可写入配置文件中
- 创建属性文件page.properties
pagedir=d:\\item\\
- 建立com.pinyougou.page.service.impl包
- 包下建立类ItemPageServiceImpl
@Service
public class ItemPageServiceImpl implements ItemPageService {
@Value("${pagedir}")
private String pagedir;
@Autowired
private FreeMarkerConfig freeMarkerConfig;
@Autowired
private TbGoodsMapper goodsMapper;
@Autowired
private TbGoodsDescMapper goodsDescMapper;
@Override
public boolean genItemHtml(Long goodsId){
try {
Configuration configuration = freeMarkerConfig.getConfiguration();
Template template = configuration.getTemplate("item.ftl");
Map dataModel=new HashMap<>();
//1.加载商品表数据
TbGoods goods = goodsMapper.selectByPrimaryKey(goodsId);
dataModel.put("goods", goods);
//2.加载商品扩展表数据
TbGoodsDesc goodsDesc = goodsDescMapper.selectByPrimaryKey(goodsId);
dataModel.put("goodsDesc", goodsDesc);
Writer out=new FileWriter(pagedir+goodsId+".html");
template.process(dataModel, out);
out.close();
return true;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
- 将item.html拷贝至web-inf/ftl下
- 修改扩展名为ftl
- 将商品名称用插值代替
<div class="sku-name">
<h4>${goods.goodsName}</h4>
</div>
在D盘建立文件夹item,将必要的样式表和Js拷贝到此目录下,此目录为生成的目录
运营商管理后台
- pinyougou-manager-web引入依赖pinyougou-page-interface
Maven->Add Dependency
- 在GoodsController.java中新增方法
@Reference(timeout=40000)
private ItemPageService itemPageService;
/**
* 生成静态页(测试)
* @param goodsId
*/
@RequestMapping("/genHtml")
public void genHtml(Long goodsId){
itemPageService.genItemHtml(goodsId);
}
商品详情页模板构建
- 模板模块化引入
此时我们的item.ftl内容较多,当我们编辑时不容易快速找到编辑的位置,所以我们将头部分拆分到head.ftl ,将尾部拆分到foot.ftl ,用include指令在item.ftl中引入 。
生成基本数据
- 在模板中找到合适的位置,用插值替换静态文本
<div class="news"><span>${goods.caption}</span></div>
<div class="fl price"><i>¥</i><em>${goods.price}</em><span>降价通知</span></div>
<div class="intro-detail"><!-- 商品详情 --> ${goodsDesc.introduction}</div>
<div id="two" class="tab-pane"><p>${goodsDesc.packageList}</p></div>
<div id="three" class="tab-pane"><p>${goodsDesc.saleService}</p></div>
- 运行控制层代码,测试生成效果
http://localhost:9101/goods/genHtml.do?goodsId=149187842867979 - 运行完控制层代码后,在D盘item文件夹中打开149187842867979.html文件
生成图片列表
- 编辑模板文件
转换图片列表的json字符串
<#--图片列表 -->
<#assign imageList=goodsDesc.itemImages?eval />
- 图片部分的代码
<!--默认第一个预览-->
<div id="preview" class="spec-preview">
<span class="jqzoom">
<#if (imageList?size>0)>
<img jqimg="${imageList[0].url}" src="${imageList[0].url}" width="400px" height="400px" />
</#if>
</span>
</div>
<!--下方的缩略图--><div class="spec-scroll">
<div class="items">
<ul>
<#list imageList as item>
<li><img src="${item.url}" bimg="${item.url}" onmousemove="preview(this)" /></li>
</#list>
</ul>
</div>
</div>
- 生成效果
生成扩展属性列表
显示在商品介绍区域
- 修改模板
<#--扩展属性列表 -->
<#--进行json转换-->
<#assign customAttributeList=goodsDesc.customAttributeItems?eval />
- 显示扩展属性数据
<#list customAttributeList as item>
<#if item.value??><#--如果扩展属性为空则不显示此条数据-->
<li>${item.text} :${item.value}</li>
</#if>
</#list>
- 效果
生成规格列表
- 修改模板 转换规格列表
<#--规格列表 -->
<#assign specificationList=goodsDesc.specificationItems?eval />
此时,我们需要使用嵌套循环
<#list specificationList as spec>
<dl>
<dt>
<div class="fl title">
<i>${spec.attributeName}</i>
</div>
</dt>
<#list spec.attributeValue as item>
<dd><a href="javascript:;" >${item}</a></dd>
</#list>
</dl>
</#list>
- 效果
生成商品类型面包屑
tb_goods表
- 修改ItemPageServiceImpl ,读取三级商品分类名称,加入到数据模型中
@Service
public class ItemPageServiceImpl implements ItemPageService {
@Autowired
private FreeMarkerConfig freeMarkerConfig;
@Autowired
private TbGoodsMapper goodsMapper;
@Autowired
private TbGoodsDescMapper goodsDescMapper;
@Autowired
private TbItemCatMapper itemCatMapper;
@Override
public boolean genItemHtml(Long goodsId){
try {
Configuration configuration = freeMarkerConfig.getConfiguration();
Template template = configuration.getTemplate("item.ftl");
Map dataModel=new HashMap<>();
//1.加载商品表数据
TbGoods goods = goodsMapper.selectByPrimaryKey(goodsId);
dataModel.put("goods", goods);
//2.加载商品扩展表数据
TbGoodsDesc goodsDesc = goodsDescMapper.selectByPrimaryKey(goodsId);
dataModel.put("goodsDesc", goodsDesc);
//3.商品分类
String itemCat1 = itemCatMapper.selectByPrimaryKey(goods.getCategory1Id()).getName();
String itemCat2 = itemCatMapper.selectByPrimaryKey(goods.getCategory2Id()).getName();
String itemCat3 = itemCatMapper.selectByPrimaryKey(goods.getCategory3Id()).getName();
dataModel.put("itemCat1", itemCat1);
dataModel.put("itemCat2", itemCat2);
dataModel.put("itemCat3", itemCat3);
Writer out=new FileWriter("d:\\item\\"+goodsId+".html");
template.process(dataModel, out);
out.close();
return true;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
}
- 修改模板,展示商品分类面包屑
<ul class="sui-breadcrumb">
<li><a href="#">${itemCat1}</a></li>
<li><a href="#">${itemCat2}</a></li>
<li><a href="#">${itemCat3}</a></li>
</ul>
- 效果
商品详情页-前端逻辑
购买数量加减操作
- 将angularJS库加入d:\item的plugins下
- 前端控制层
-
将base.js拷贝到js目录下
-
在js目录下构建controller文件夹,创建itemController.js
-
//商品详细页(控制层)
app.controller('itemController',function($scope){
//数量操作
$scope.addNum=function(x){
$scope.num=$scope.num+x;
if($scope.num<1){
$scope.num=1;
}
}
});
增加就传入正数,减少就传入负数,数量不能小于1
-
模板
-
引入js
<script type="text/javascript" src="plugins/angularjs/angular.min.js"> </script>
<script type="text/javascript" src="js/base.js"> </script>
<script type="text/javascript" src="js/controller/itemController.js"> </script>
- 添加指令
<body ng-app="pinyougou" ng-controller="itemController" ng-init="num=1">
- 调用操作数量的方法
<div class="controls">
<input autocomplete="off" type="text" value="{{num}}" minnum="1" class="itxt" />
<a href="javascript:void(0)" class="increment plus" ng-click="addNum(1)" >+</a>
<a href="javascript:void(0)" class="increment mins" ng-click="addNum(-1)">-</a>
</div>
- 效果
规格选择
创建一个对象 格式为:{‘网络’:’移动3G’,“机身内存”:“64G”} 用于存储用户选择的规格
点击标签的时候,更改此对象
前端控制层创建方法,用于判断当前规格与选项是否被选中
- 前端控制层
修改itemController.js
$scope.specificationItems={};//记录用户选择的规格
//用户选择规格
$scope.selectSpecification=function(name,value){
$scope.specificationItems[name]=value;
}
//判断某规格选项是否被用户选中
$scope.isSelected=function(name,value){
if($scope.specificationItems[name]==value){
return true;
}else{
return false;
}
}
- 模板
页面调用控制器的方法
<#list specificationList as spec>
<dl>
<dt>
<div class="fl title">
<i>${spec.attributeName}</i>
</div>
</dt>
<#list spec.attributeValue as item>
<dd><a href="javascript:;"
class="{{isSelected('${spec.attributeName}','${item}')?'selected':''}}"
ng-click="selectSecification('${spec.attributeName}','${item}')">${item}
<span title="点击取消选择"> </span>
</a></dd>
</#list>
</dl>
</#list>
如果被选中,会有红色的框样式,即class=“selected”,执行isSelected()方法,采用三元运算符来判断是否被选中,选中则加样式
- 效果
商品详情页-读取SKU信息
SPU = Standard Product Unit (标准产品单位)SPU是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。
在商品信息电子化过程中,商品的特性可以由多个“属性|属性值对”进行描述。“属性|属性值对”完全相同的商品,可以抽象成为一个SPU。
另一方面,这些“属性|属性值对”在SPU中固化下来,逐步标准化。
基于SPU的商品信息结构,可以实现丰富的应用,比如商品信息与资讯、评论、以及其它SPU的整合。
例如:iPhone X 可以确定一个产品即为一个SPU。
SKU=Stock Keeping Unit(库存量单位)。
针对电商而言:
1、SKU是指一款商品,每款都有出现一个SKU,便于电商品牌识别商品。
2、一款商品多色,则是有多个SKU,例:一件衣服,有红色、白色、蓝色,则SKU编码也不相同,如相同则会出现混淆,发错货。
例如:iPhone X 64G 银色 则是一个SKU。
需求:当我们选择规格后,应该在页面上更新商品名称为SKU的商品标题,价格也应该为SKU的商品价格。
页面生成SKU列表变量
- 后端服务层
- 修改pinyougou-page-service的ItemPageServiceImpl.java
@Autowired
private TbItemMapper itemMapper;
@Override
public boolean genItemHtml(Long goodsId) {
Configuration configuration = freeMarkerConfigurer.getConfiguration();
try {
Template template = configuration.getTemplate("item.ftl");
//创建数据模型
Map dataModel=new HashMap<>();
//1.商品主表数据
TbGoods goods = goodsMapper.selectByPrimaryKey(goodsId);
dataModel.put("goods", goods);
//2.商品扩展表数据
TbGoodsDesc goodsDesc = goodsDescMapper.selectByPrimaryKey(goodsId);
dataModel.put("goodsDesc", goodsDesc);
//3.读取商品分类
String itemCat1 = itemCatMapper.selectByPrimaryKey(goods.getCategory1Id()).getName();
String itemCat2 = itemCatMapper.selectByPrimaryKey(goods.getCategory2Id()).getName();
String itemCat3 = itemCatMapper.selectByPrimaryKey(goods.getCategory3Id()).getName();
dataModel.put("itemCat1", itemCat1);
dataModel.put("itemCat2", itemCat2);
dataModel.put("itemCat3", itemCat3);
//4.读取SKU列表数据
TbItemExample example=new TbItemExample();
Criteria criteria = example.createCriteria();
criteria.andGoodsIdEqualTo(goodsId);//SPU ID
criteria.andStatusEqualTo("1");//状态有效
example.setOrderByClause("is_default desc");//按是否默认字段降序排序,目的是返回的结果第一条为默认SKU
List<TbItem> itemList = itemMapper.selectByExample(example);
dataModel.put("itemList", itemList);
Writer out=new FileWriter(pagedir+goodsId+".html");
template.process(dataModel, out);//输出
out.close();
return true;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
return false;
}
}
- 模板
- 修改模板
<script>
//SKU商品列表
var skuList=[
<#list itemList as item>
{
"id":${item.id?c},
"title":"${item.title!''}",
"price":${item.price?c},
"spec": ${item.spec}
} ,
</#list>
];
</script>
${item.id?c} c函数将数字转换成字符串(去除数字间的逗号)
- 测试(执行控制层方法)生成,发现页面源代码中生成了变量
显示SKU标题和价格
- 加载默认SKU信息
- 修改itemController.js
//加载默认SKU
$scope.loadSku=function(){
$scope.sku=skuList[0];
//深克隆
$scope.specificationItems= JSON.parse(JSON.stringify($scope.sku.spec)) ;
}
- 修改模板item.ftl
<body ng-app="pinyougou" ng-controller="itemController" ng-init="num=1;loadSku()">
- 修改模板,显示标题
<div class="sku-name"><h4>{{sku.title}}</h4></div>
- 显示价格
<div class="summary-wrap">
<div class="fl title"><i>价 格</i></div>
<div class="fl price"><i>¥</i> <em>{{sku.price}}</em> <span>降价通知</span></div>
</div>
选择规格更新SKU
- 修改itemController.js , 编写匹配对象的方法
//匹配两个对象是否相等
matchObject=function(map1,map2){
for(var k in map1){
if(map1[k]!=map2[k]){
return false;
}
}
for(var k in map2){
if(map2[k]!=map1[k]){
return false;
}
}
return true;
}
- 编写方法,在SKU列表中查询当前用户选择的SKU
//根据规格查询sku
searchSku=function(){
for(var i=0;i<skuList.length;i++){
if(matchObject(skuList[i].spec,$scope.specificationItems)){
$scope.sku=skuList[i];
return;
}
}
$scope.sku={id:0,title:'------',price:0};//如果没有匹配的
}
- 在用户选择规格后触发读取方法
//用户选择规格
$scope.selectSpecification=function(name,value){
$scope.specificationItems[name]=value;
searchSku();//读取sku
}
- 效果