Authored by htoooth

refactor

... ... @@ -23,13 +23,15 @@ const CHANNEL = {
};
const _getChannel = (req, res) => {
const channel = req.cookies._Channel || '';
let channel = req.cookies._Channel;
if (!channel) {
res.cookie('_Channel', CHANNEL.boys, {
domain: '.yohobuy.com',
maxAge: moment.duration(300, 'days').seconds()
});
channel = 'boys'; // 设置默认值
}
return channel;
... ... @@ -47,30 +49,25 @@ const _getGender = (channel) => {
};
/**
* 渲染商品详情
* 首屏渲染商品详情
*/
const showMain = (req, res, next) => {
let pid = req.params[0];
let gid = req.params[1];
let channel = _getChannel(req, res);
let gender = _getGender(channel);
let saveCurrentGoodsInCookies = _.partial(service.saveRecentGoodInCookies,
req.cookies._browse,
req.cookies._browseskn,
res
);
return service.showMainAsync({
pid: pid,
gid: gid,
channel: channel,
gender: gender,
const nullUserInfo = {
uid: null,
isStudent: null,
vipLevel: null,
saveInCookies: saveCurrentGoodsInCookies
}).then(result=> {
saveInCookies: null
};
return service.showMainAsync(Object.assign({
pid: pid,
channel: channel,
gender: gender
}, nullUserInfo)).then(result=> {
return res.render('product/detail', Object.assign({
module: 'product',
page: 'detail',
... ... @@ -84,6 +81,47 @@ const showMain = (req, res, next) => {
};
const detailHeader = (req, res, next) => {
let pid = req.query.productId || 0;
let uid = req.user.uid || 0;
let vipLevel = detailHelper.vipLevel(req.user.vip);
let dataMd5 = req.query.md5 || 0;
let saveCurrentGoodsInCookies = _.partial(service.saveRecentGoodInCookies,
req.cookies._browseskn,
res
);
return service.getDetailHeader(pid, uid, req.user.isStudent, vipLevel, dataMd5, saveCurrentGoodsInCookies)
.then((result) => {
if (result.code === 200) {
return res.render('product/detail-header', Object.assign({layout: false}, result.data));
} else {
return res.status(204).end();
}
}).catch(next);
};
const detailReturn = (req, res, next) => {
let skn = req.query.skn || 0;
if (!skn) {
return {
code: 400,
message: '商品数据出错'
};
}
return service.saleReturn(skn).then(result => {
return res.json({
code: 200,
data: {
result: result
}
});
}).catch(next);
};
/**
* 获取热区图
*/
... ... @@ -116,8 +154,8 @@ const indexComment = (req, res, next) => {
return {
avatar: _.get(item, 'userInfo.headIco', '') ?
helpers.image(item.userInfo.headIco, 30, 30) :
DEFAULT_AVATAR_ICO,
helpers.image(item.userInfo.headIco, 30, 30) :
DEFAULT_AVATAR_ICO,
userName: _.get(item, 'userInfo.nickName', ''),
date: moment(item.createTime, 'X').format('YYYY-MM-DD HH:mm:ss'),
color: _.get(item, 'goods.color_name', ''),
... ... @@ -182,22 +220,6 @@ const createConsult = (req, res, next) => {
}
};
const detailHeader = (req, res, next) => {
let pid = req.query.productId || 0;
let uid = req.user.uid || 0;
let vipLevel = detailHelper.vipLevel(req.user.vip);
let dataMd5 = req.query.md5 || 0;
// service.getDetailHeader(pid, uid, req.user.isStudent, vipLevel, dataMd5).then((result) => {
// if (result.code === 200) {
// return res.render('product/detail-header', Object.assign({layout: false}, result.data));
// } else {
// return res.status(204).end();
// }
// }).catch(next);
return res.status(204).end();
};
module.exports = {
showMain,
... ... @@ -205,8 +227,8 @@ module.exports = {
indexComment,
indexConsult,
createConsult,
productHeader: detailHeader
productHeader: detailHeader,
detailReturn
};
... ...
... ... @@ -486,25 +486,16 @@ function _getSortNavAsync(smallSortId, gender) {
}
// 保存在 gids 和 skns ,最近流览功能
const saveRecentGoodInCookies = (oldGids, oldSkns, res, addGids, addSkns) => {
oldGids = (oldGids || '').split(',');
oldSkns = (oldSkns || '').split(',');
addSkns = `${addSkns}-${addGids}`;
const saveRecentGoodInCookies = (oldSkns, res, addSkns) => {
oldSkns = oldSkns || [];
_.remove(oldGids, addGids);
_.remove(oldSkns, addSkns);
oldGids.unshift(addGids);
oldSkns.unshift(addSkns);
res.cookie('_browse', oldGids.splice(0, 30).join(','), {
maxAge: 2000000000,
domain: 'yohobuy.com'
});
res.cookie('_browseskn', oldSkns.splice(0, 30).join(','), {
maxAge: 2000000000,
domain: 'yohobuy.com'
domain: '.yohobuy.com'
});
};
... ... @@ -1052,7 +1043,7 @@ const _getSeoByGoodsInfo = (goodsInfo, navs) => {
* @param origin Object 原始数据
* @return result Object 格式化数据
*/
const _detailDataPkg = (origin, uid, vipLevel) => {
const _detailDataPkg = (origin, uid, vipLevel, cookies) => {
return co(function*() {
let result = {}; // 结果输出
let md5 = origin.md5;
... ... @@ -1286,19 +1277,25 @@ const _detailDataPkg = (origin, uid, vipLevel) => {
}
}
// 最近浏览功能 ,限量商品不加入到最近浏览
if (!_.has(result, 'fashionTopGoods')) {
cookies && cookies(_.get(result, 'skn', ''));
}
return {
goodsInfo: result,
banner: _.isEmpty(bandInfo) ? null : bandInfo,
statGoodsInfo: statGoodsInfo
statGoodsInfo: statGoodsInfo,
latestWalk: 5 // 只显示5个
};
})();
};
const getDetailHeader = (pid, uid, isStudent, vipLevel, dataMd5) => {
let currentUserProductInfo = _.partial(_detailDataPkg, _, uid, vipLevel);
const getDetailHeader = (pid, uid, isStudent, vipLevel, dataMd5, cookie) => {
let currentUserProductInfo = _.partial(_detailDataPkg, _, uid, vipLevel, cookie);
return productAPI.getProductAsync(pid, uid, isStudent, vipLevel)
.then(result => currentUserProductInfo(result))
.then(currentUserProductInfo)
.then((result) => {
if (result.goodsInfo.md5 !== dataMd5 || uid) {
return {
... ... @@ -1314,16 +1311,20 @@ const getDetailHeader = (pid, uid, isStudent, vipLevel, dataMd5) => {
});
};
const saleReturn = (skn) => {
return productAPI.isSupportReturnedSale(skn).then(result => _.get(result, `data.${skn}`, 'N') === 'N' ? 'Y' : 'N');
};
/**
* 获取某一个商品详情主页面
*/
const showMainAsync = (data) => {
return co(function * () {
let currentUserProductInfo = _.partial(_detailDataPkg, _, data.uid, data.vipLevel);
let curUserProduct = _.partial(_detailDataPkg, _, data.uid, data.vipLevel, data.gid, data.saveInCookies);
// 获取并处理商品基本信息
let productInfo = yield productAPI.getProductAsync(data.pid, data.uid, data.isStudent, data.vipLevel)
.then(currentUserProductInfo);
.then(curUserProduct);
if (_.isEmpty(productInfo) || _.isEmpty(productInfo.goodsInfo)) {
return Promise.reject({
... ... @@ -1334,7 +1335,7 @@ const showMainAsync = (data) => {
let requestData = yield Promise.all([
_getSortNavAsync(productInfo.goodsInfo.smallSortId, data.gender), // 导航
HeaderModel.requestHeaderData(data.channel), // 头部数据
productAPI.isSupportReturnedSale(productInfo.goodsInfo.skn), // 退换货
productAPI.isSupportReturnedSale(productInfo.goodsInfo.skn), // 退换货 TODO:
_getProductIntroAsync(productInfo.goodsInfo.productId, productInfo.goodsInfo.skn) // 商品详细介绍
]);
... ... @@ -1375,13 +1376,6 @@ const showMainAsync = (data) => {
[{name: productInfo.goodsInfo.name}]
);
// 最近浏览功能 ,限量商品不加入到最近浏览
if (!_.has(productInfo, 'goodsInfo.fashionTopGoods')) {
data.saveInCookies(data.gid, _.get(productInfo, 'goodsInfo.skn', ''));
}
// 只显示5个
result.detail.latestWalk = 5;
return result;
})();
... ... @@ -1395,5 +1389,6 @@ module.exports = {
showMainAsync: showMainAsync, // 获取某一个商品详情主页面
indexHotAreaAsync: hotAreaService.indexAsync, // 获取某一个商品的热区数据
saveRecentGoodInCookies, // 保存最近的商品
getDetailHeader
getDetailHeader,
saleReturn
};
... ...
... ... @@ -59,6 +59,8 @@ router.post('/detail/consult', auth, detail.createConsult);// 创建咨询
router.get('/detail/hotarea', detail.indexHotArea);// 商品热区
router.post('/index/favoriteBrand', favorite.changeFavoriteBrand);// 收藏品牌
router.post('/item/togglecollect', favorite.collectProduct); // 收藏商品
router.get('/detail/header', detail.productHeader); // 价格数据重新获取接口
router.get('/detail/return', detail.detailReturn);// 特殊商品退换货
// 搜索
router.get('/search/index', search.index);
... ...
... ... @@ -23,7 +23,8 @@
{{> common/path-nav}}
{{# goodsInfo}}
<div class="main clearfix" data-skn="{{skn}}" data-id="{{productId}}">
<div class="main clearfix" data-skn="{{skn}}" data-id="{{productId}}" data-md5="{{md5}}"
data-skn="{{skn}}">
<div class="pull-left imgs clearfix">
<div class="pull-left img">
<div class="tags clearfix">
... ... @@ -540,12 +541,18 @@
<span class="title cur">商品详情 DETAILS</span>
</p>
<div id="details-html" class="details-html">
<div class="lazy-load-object">
<textarea class="datalazyload" style="visibility: hidden;">
<script>fetchHotArea();</script>
</textarea>
</div>
{{{details}}}
</div>
</div>
{{# consultComment}}
<div class="consult-comment info-block">
<p class="block-title">
<span class="title">顾客咨询(<em class="consult-num">0</em></span>
<span class="sep">|</span>
... ... @@ -599,6 +606,14 @@
</p>
</div>
</div>
<div class="lazy-load-object">
<textarea class="datalazyload" style="visibility: hidden;">
<script>fetchComment();
fetchReturn();</script>
</textarea>
</div>
</div>
{{/ consultComment}}
... ... @@ -635,11 +650,8 @@
</div>
</div>
{{#if supportSaleReturnedService}}
<div class="support-saleReturned-service"></div>
{{^}}
<div class="not-support-saleReturned-service"></div>
{{/if}}
<div class="support-saleReturned-service"></div>
<div class="service"></div>
{{#if latestWalk}}
... ... @@ -724,7 +736,8 @@
(function() {
var mvl = document.createElement('script');
mvl.type = 'text/javascript'; mvl.async = true;
mvl.type = 'text/javascript';
mvl.async = true;
mvl.src = ('https:' == document.location.protocol ? 'https://static-ssl.mediav.com/mvl.js' : 'http://static.mediav.com/mvl.js');
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(mvl, s);
... ...
/* eslint-disable */
/**
* Created by TaoHuang on 2016/10/10.
*/
function dataLazyLoad(doc) {
// 兼容低版本 IE
Function.prototype.bind = Function.prototype.bind || function(context) {
var that = this;
return function() {
return that.apply(context, arguments);
};
};
// 工具方法 begin
var Util = {
getElementsByClassName: function(cls) {
if (doc.getElementsByClassName) {
return doc.getElementsByClassName(cls);
}
var o = doc.getElementsByTagName("*"),
rs = [];
for (var i = 0, t, len = o.length; i < len; i++) {
(t = o[i]) && ~t.className.indexOf(cls) && rs.push(t);
}
return rs;
},
addEvent: function(ele, type, fn) {
ele.attachEvent ? ele.attachEvent("on" + type, fn) : ele.addEventListener(type, fn, false);
},
removeEvent: function(ele, type, fn) {
ele.detachEvent ? ele.detachEvent("on" + type, fn) : ele.removeEventListener(type, fn, false);
},
getPos: function(ele) {
var pos = {
x: 0,
y: 0
};
while (ele.offsetParent) {
pos.x += ele.offsetLeft;
pos.y += ele.offsetTop;
ele = ele.offsetParent;
}
return pos;
},
getViewport: function() {
var html = doc.documentElement;
return {
w: !window.innerWidth ? html.clientHeight : window.innerWidth,
h: !window.innerHeight ? html.clientHeight : window.innerHeight
};
},
getScrollHeight: function() {
html = doc.documentElement, bd = doc.body;
return Math.max(window.pageYOffset || 0, html.scrollTop, bd.scrollTop);
},
getEleSize: function(ele) {
return {
w: ele.offsetWidth,
h: ele.offsetHeight
};
}
};
// 工具方法 end
return {
threshold: 0, // {number} 阈值,预加载高度,单位(px)
els: null, // {Array} 延迟加载元素集合(数组)
fn: null, // {Function} scroll、resize、touchmove 所绑定方法,即为 pollTextareas()
evalScripts: function(code) {
var head = doc.getElementsByClassName("yoho-footer")[0],
js = doc.createElement("script");
js.text = code;
head.appendChild(js, head.firstChild);
head.removeChild(js);
},
evalStyles: function(code) {
var head = doc.getElementsByTagName("head")[0],
css = doc.createElement("style");
css.type = "text/css";
try {
css.appendChild(doc.createTextNode(code));
} catch (e) {
css.styleSheet.cssText = code;
}
head.appendChild(css);
},
extractCode: function(str, isStyle) {
var cata = isStyle ? "style" : "script",
scriptFragment = "<" + cata + "[^>]*>([\\S\\s]*?)</" + cata + "\\s*>",
matchAll = new RegExp(scriptFragment, "img"),
matchOne = new RegExp(scriptFragment, "im"),
matchResults = str.match(matchAll) || [],
ret = [];
for (var i = 0, len = matchResults.length; i < len; i++) {
var temp = (matchResults[i].match(matchOne) || ["", ""])[1];
temp && ret.push(temp);
}
return ret;
},
decodeHTML: function(str) {
return str.replace(/</g, "<").replace(/>/g, ">").replace(/&/g, "&")
.replace(/&lt;/g, '<').replace(/&gt;/g, '>');
},
insert: function(ele) {
var parent = ele.parentNode,
txt = this.decodeHTML(ele.innerHTML),
matchStyles = this.extractCode(txt, true),
matchScripts = this.extractCode(txt);
parent.innerHTML = txt
.replace(new RegExp("<script[^>]*>([\\S\\s]*?)</script\\s*>", "img"), "")
.replace(new RegExp("<style[^>]*>([\\S\\s]*?)</style\\s*>", "img"), "");
if (matchStyles.length) {
for (var i = matchStyles.length; i--;) {
this.evalStyles(matchStyles[i]);
}
}
// 如果延迟部分需要做 loading 效果
parent.className = parent.className.replace("loading", "");
if (matchScripts.length) {
for (var i = 0, len = matchScripts.length; i < len; i++) {
this.evalScripts(matchScripts[i]);
}
}
},
inView: function(ele) {
var top = Util.getPos(ele).y
, viewVal = Util.getViewport().h
, scrollVal = Util.getScrollHeight()
, eleHeight = Util.getEleSize(ele).h;
if (top >= scrollVal - eleHeight - this.threshold && top <= scrollVal + viewVal + this.threshold) {
return true;
}
return false;
},
pollTextareas: function() {
// 需延迟加载的元素已经全部加载完
if (!this.els.length) {
Util.removeEvent(window, "scroll", this.fn);
Util.removeEvent(window, "resize", this.fn);
Util.removeEvent(doc.body, "touchMove", this.fn);
return;
}
// 判断是否需要加载
for (var i = this.els.length; i--;) {
var ele = this.els[i];
if (!this.inView(ele)) {
continue;
}
this.insert(ele);
this.els.splice(i, 1);
}
},
init: function(config) {
var cls = config.cls;
this.threshold = config.threshold ? config.threshold : 0;
this.els = Array.prototype.slice.call(Util.getElementsByClassName(cls));
this.fn = this.pollTextareas.bind(this);
this.fn();
Util.addEvent(window, "scroll", this.fn);
Util.addEvent(window, "resize", this.fn);
Util.addEvent(doc.body, "touchMove", this.fn);
}
};
}
module.exports = dataLazyLoad;
/**
* demo
* datalazyload.init({
* cls: "datalazyload", // 需要延迟加载的类,即 textarea 的类名
* threshold: 100 // 距离底部多高,进行延迟加载的阈值
* });
*/
... ...
... ... @@ -11,13 +11,15 @@
var $ = require('yoho-jquery'),
lazyLoad = require('yoho-jquery-lazyload'),
dataLazyLoad = require('../plugins/lazy-load')(document),
Handlebars = require('yoho-handlebars');
var bindEvent = $.Callbacks(); // eslint-disable-line
var $main = $('.main'),
id = $main.data('id'),
md5 = $main.data('md5');
md5 = $main.data('md5'),
skn = $main.data('skn');
var SLIDETIME = 200;
... ... @@ -27,6 +29,8 @@ var colTxt = {
hover: '取消收藏'
};
var $saleReturn = $('#saleReturn');
bindEvent.add(function() {
var $imgShow = $('#img-show'),
$thumbs = $('#thumbs > .thumb-wrap');
... ... @@ -549,33 +553,41 @@ $('.after-service-switch').click(function() {
});
// 商品详情区的热点
$.ajax({
type: 'GET',
url: '/product/detail/hotarea',
data: {
productId: id
}
}).then(function(html) {
$('#details-html').prepend(html);
// 修正热区尺寸使居中
$('.hot-point-wrap > img').load(function() {
$(this).parent('.hot-point-wrap').width($(this).width());
});
function fetchHotArea() {
$.ajax({
type: 'GET',
url: '/product/detail/hotarea',
data: {
productId: id
}
}).then(function(html) {
$('#details-html').prepend(html);
// Bind Hover event
$('.hot-point').hover(function() {
$(this).addClass('hover');
}, function() {
$(this).removeClass('hover');
// 修正热区尺寸使居中
$('.hot-point-wrap > img').load(function() {
$(this).parent('.hot-point-wrap').width($(this).width());
});
// Bind Hover event
$('.hot-point').hover(function() {
$(this).addClass('hover');
}, function() {
$(this).removeClass('hover');
});
});
});
}
// 商品详情懒加载
window.fetchHotArea = fetchHotArea;
// 商品详情图片懒加载
lazyLoad($('#details-html img'));
// 数据懒加载
dataLazyLoad.init({cls: 'datalazyload', threshold: -50});
// 咨询和评价
(function() {
function fetchComment() {
var commentPage = 1,
consultPage = 1;
... ... @@ -762,7 +774,28 @@ lazyLoad($('#details-html img'));
loadComments();
loadConsults();
}());
}
window.fetchComment = fetchComment;
// 特殊商品退换货
function fetchReturn() {
return $.ajax({
type: 'GET',
url: '/product/detail/return',
data: {
skn: skn
}
}).then(function(result) {
if (result.code === 200) {
if (result.data.result === 'N') {
$saleReturn.removeClass().addClass('not-support-saleReturned-service');
}
}
});
}
window.fetchReturn = fetchReturn;
// 首屏加载,绑定事件
bindEvent.fire();
... ...