因为生成静态页面属于一个单独的功能,我们新建一个单独的工程来实现
1. 服务层
1)创建pinyougou-page-interface工程,创建com.pinyougou.page.service包,包下创建接口
2)创建war工程pinyougou-page-service
3)依赖
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
4)spring配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:dubbo="http://code.alibabatech.com/schema/dubbo" xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 访问Dubbox所要占用的端口(自己要占用的端口) 该配置这里不可以省略,否则后面会出现端口占用的情况-->
<dubbo:protocol name="dubbo" port="20885"></dubbo:protocol>
<dubbo:application name="pinyougou-page-service"/>
<!-- 这里的端口是服务端提供的端口号,是服务器上注册中心提供的端口 -->
<dubbo:registry address="zookeeper://192.168.25.130:2181"/>
<dubbo:annotation package="com.pinyougou.page.service.impl" />
<!-- 配置freemarker -->
<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>
</beans>
5)其他配置
6)代码编写
package com.pinyougou.page.service.impl;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer;
import com.alibaba.dubbo.config.annotation.Service;
import com.pinyougou.mapper.TbGoodsDescMapper;
import com.pinyougou.mapper.TbGoodsMapper;
import com.pinyougou.mapper.TbItemMapper;
import com.pinyougou.page.service.ItemPageService;
import com.pinyougou.pojo.TbGoods;
import com.pinyougou.pojo.TbGoodsDesc;
import com.pinyougou.pojo.TbItem;
import freemarker.core.ParseException;
import freemarker.template.Configuration;
import freemarker.template.MalformedTemplateNameException;
import freemarker.template.Template;
import freemarker.template.TemplateNotFoundException;
/**商品详细页实现类
* @author Administrator
*
*/
@Service(timeout=50000)
public class ItemPageServiceImpl implements ItemPageService{
@Resource
private TbGoodsMapper goodsMapper;
@Resource
private TbGoodsDescMapper goodsDescMapper;
@Resource
private FreeMarkerConfigurer freeMarkerConfigurer;
@Value("${pageDir}")
private String pageDir;
@Override
public boolean genItemHtml(Long goodsId) {
Writer out = null;
try {
// 获取配置对象
Configuration configuration = freeMarkerConfigurer.getConfiguration();
// 获取模板对象
Template template = configuration.getTemplate("item.ftl");
// 创建数据模型
Map dataModel = new HashMap();
// 1. 查询SPU信息
TbGoods goods = goodsMapper.selectByPrimaryKey(goodsId);
dataModel.put("goods", goods);
// 2. 查询SPU详情
TbGoodsDesc goodsDesc = goodsDescMapper.selectByPrimaryKey(goodsId);
dataModel.put("goodsDesc", goodsDesc);
// 创建目标文件的输出流对象
out = new FileWriter(pageDir + goodsId + ".html");
// 写数据到目标文件
template.process(dataModel,out);
return true;
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally{
if(null != out){
try {
out.close();
} catch (IOException e) {
System.out.println("关闭流文件失败");
}
}
}
return false;
}
}
7)模板改造
<div class="sku-name">
<h4>${goods.goodsName}</h4>
</div>
详情页什么时候生成了?应该是在商品审核之后,就要生成该文件,所以在运营商后台(manager-web)调用该工程
2. 控制层
2.1 测试服务方法
先做个测试,测试服务是否好用,在goodsController中新增一个方法genHtml
@RequestMapping("/genHtml")
public void genHtml(Long goodsId){
boolean flag = itemPageService.genItemHtml(goodsId);
if(flag){
System.out.println("生成文件成功!");
} else{
System.out.println("生成文件失败!");
}
}
然后启动服务,在页面输入http://localhost:9101/goods/genHtml.do?goodsId=149187842867971 查看item目录文件是否生成
文件生成,打开文件,如图:
商品标题生成成功。
3. 商品详情页模板构建
3.1 模板模块化引入
此时我们的item.ftl内容较多,当我们编辑时不容易快速找到编辑的位置,所以我们将头部分拆分到head.ftl ,将尾部拆分到foot.ftl ,用include指令在item.ftl中引入 。
3.2 改造模板文件
3.2.1 生成基础数据
在模板中找到合适的位置,用插值替换静态文本
<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>
3.2.2 生成图片列表
编辑模板文件
<#--图片列表 -->
<#assign imageList=goodsDesc.itemImages?eval />
这一句要转换图片列表的json字符串
图片部分的代码
<!--默认第一个预览-->
<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>
3.2.3 生成扩展属性列表
修改模板 首先进行json转换
<#--扩展属性列表 -->
<#assign customAttributeList=goodsDesc.customAttributeItems?eval />
显示扩展属性数据,如果扩展属性为空则不显示此条数据
<#list customAttributeList as item>
<#if item.value??>
<li>${item.text} :${item.value}</li>
</#if>
</#list>
3.2.4 生成规格列表
修改模板 转换规格列表
<#--规格列表 -->
<#assign specificationList=goodsDesc.specificationItems?eval />
此时,我们需要使用嵌套循环
<#list specificationList as specification>
<dl>
<dt>
<div class="fl title">
<i>${specification.attributeName}</i>
</div>
</dt>
<#list specification.attributeValue as item>
<dd><a href="javascript:;" >${item}</a></dd>
</#list>
</dl>
</#list>
3.2.5 生成商品类型面包屑
修改ItemPageServiceImpl ,读取三级商品分类名称,加入到数据模型中
//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);
修改模板,展示商品分类面包屑
<ul class="sui-breadcrumb">
<li><a href="#">${itemCat1}</a></li>
<li><a href="#">${itemCat2}</a></li>
<li><a href="#">${itemCat3}</a></li>
</ul>
4. 前端逻辑
4.1 购买数量加减操作
1)在item目录添加angularJS文件
2)前端控制层
将base.js拷贝到js目录下
在js目录下构建controller文件夹,创建itemController.js
app.controller("itemController",function($scope){
$scope.num = 1;
// 数量加减
$scope.addNum = function(x){
$scope.num += x;
if( $scope.num < 1 ){
$scope.num = 1;
}
}
});
注意:要控制一下数量不能小于1
3)在模板中引入头文件与添加指令
引入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">
4)调用方法
<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>
4.2 规格选择
最终我们需要实现的效果:
实现思路:
创建一个对象({ '网络':'移动4G' }),用于存储用户选择的规格
点击标签的时候,更改此对象
前端控制层创建方法,用于判断当前规格与选项是否被选中
4.2.1 前端控制层
修改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;
}
}
4.2.2 修改模板
<dd>
<a class="{{isSelected('${specification.attributeName}','${item}')?'selected':''}}"
ng-click="selectSpecification('${specification.attributeName}','${item}')">
${item}
<span title="点击取消选择"> </span>
</a>
</dd>
5.2 SKU信息的读取
当我们选择规格后,应该在页面上更新商品名称为SKU的商品标题,价格也应该为SKU的商品价格。
5.2.1 页面生成SKU列表变量
1)后端代码
// 4. 读取SKU列表数据
TbItemExample example = new TbItemExample();
Criteria criteria = example.createCriteria();
criteria.andGoodsIdEqualTo(goodsId);
criteria.andStatusEqualTo("1");// 状态有效
example.setOrderByClause("is_default desc"); // 按是否默认降序排序,目的是让返回的第一条结果为默认的sku
List<TbItem> itemList = itemMapper.selectByExample(example);
dataModel.put("itemList", itemList);
2)修改模板
<script>
//SKU商品列表
var skuList=[
<#list itemList as item>
{
"id":${item.id?c},
"title":"${item.title!''}",
"price":${item.price?c},
"spec": ${item.spec}
} ,
</#list>
];
</script>
5.2.2 显示SKU标题和价格
1)加载默认sku信息
//加载默认SKU
$scope.loadSku=function(){
$scope.sku=skuList[0];
$scope.specificationItems= JSON.parse(JSON.stringify($scope.sku.spec)) ;
}
2)修改模板
1. 调用方法
<body ng-app="pinyougou" ng-controller="itemController" ng-init="num=1;loadSku()">
2. 显示标题等信息
<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>
3)选择规格更新sku
// 4. 规格的联动效果
// 4.1 定义函数,用于比较两个json对象是否相等
matchjson = function(obj1,obj2){
for(var key in obj1){
if(obj1[key] != obj2[key]){
return false;
}
}
for(var key in obj2){
if(obj2[key] != obj1[key]){
return false;
}
}
return true;
}
// 4.2 定义查询函数
searchSku = function(){
for( var i = 0 ; i < skuList.length ; i ++ ){
if( matchjson(skuList[i].spec,$scope.specs) ){
$scope.sku = skuList[i];
return;
}
}
$scope.sku = {id:-1,title:'该规格没有商品',price:-1,spec:{}};
}
6. 商品审核生成详细页
修改pinyougou-manager-web的GoodsController.java
@RequestMapping("/updateStatus")
public Result updateStatus(Long[] ids,String status){
try {
goodsService.updateStatus(ids, status);
//按照SPU ID查询 SKU列表(状态为1)
if(status.equals("1")){//审核通过
List<TbItem> itemList = goodsService.findItemListByGoodsIdandStatus(ids, status);
//调用搜索接口实现数据批量导入
if(itemList.size()>0){
itemSearchService.importList(itemList);
}else{
System.out.println("没有明细数据");
}
//静态页生成
for(Long goodsId:ids){
itemPageService.genItemHtml(goodsId);
}
}
return new Result(true, "修改状态成功");
} catch (Exception e) {
e.printStackTrace();
return new Result(false, "修改状态失败");
}
}