Authored by htoooth

Merge branch 'feature/cart-product-info' of http://git.yoho.cn/fe/yohobuy-node

/**
* Created by TaoHuang on 2016/10/19.
*/
'use strict';
const service = require('../models/cart-service');
const getProductInfo = (req, res, next) => {
let pid = req.query.productId || '';
service.getProductInfoAsync(pid).then((result) => {
return res.render('goods-detail', Object.assign({
layout: false
}, result));
}).catch(next);
};
module.exports = {
getProductInfo
};
... ...
/**
* sub app cart
* @author: htoooth<ht.anglenx@gmail.com>
* @date: 2016/10/19
*/
'use strict';
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
var app = express();
// set view engin
var doraemon = path.join(__dirname, '../../doraemon/views'); //parent view root
app.on('mount', function(parent) {
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
});
app.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
// router
app.use(require('./router'));
module.exports = app;
... ...
/**
* 商品详情models
* @author: xuqi<qi.xu@yoho.cn>
* @date: 2016/5/6
*/
'use strict';
const Promise = require('bluebird');
const co = Promise.coroutine;
const _ = require('lodash');
const helpers = global.yoho.helpers;
const productAPI = require('./product-api');
const _getProductIntroAsync = (productSkn) => {
return co(function * () {
let result = yield Promise.props({
sizeInfo: productAPI.sizeInfoAsync(productSkn)
});
return result;
})();
};
/**
* 获得sku商品数据
*/
const _getSkuDataByProductBaseInfo = (data) => {
let totalStorageNum = 0;
let skuGoods = null;// sku商品
let defaultImage = '';// 默认图
let chooseSkuFlag = false; // 选中状态
if (_.isEmpty(_.get(data, 'goods_list', []))) {
return {
totalStorageNum,
skuGoods,
defaultImage
};
}
skuGoods = _.get(data, 'goods_list', []).reduce((acc, cur, pos)=> {
// 如果status为0,即skc下架时就跳过该商品$value['status'] === 0
let goodsGroup = {};
if (_.isEmpty(cur.color_image)) {
return acc;
}
if (cur.images_list) {
// 商品列表
goodsGroup.productSkc = cur.product_skc;
goodsGroup.src = helpers.image(cur.color_image, 40, 40);
goodsGroup.title = `${_.trim(data.product_name)} ${cur.color_name}`;
goodsGroup.name = cur.color_name;
goodsGroup.focus = false;
goodsGroup.total = 0;
goodsGroup.thumbs = [];
goodsGroup.size = [];
}
_.get(cur, 'images_list', []).forEach((good) => {
if (good.image_url) {
goodsGroup.thumbs.push({
url: '',
shower: helpers.image(good.image_url, 420, 560),
img: helpers.image(good.image_url, 75, 100)
});
}
});
// 缩略图空,不显示
if (_.isEmpty(goodsGroup.thumbs)) {
return acc;
}
// 默认第一张图片
if (pos === 0) {
defaultImage = helpers.image(cur.color_image, 420, 560);
}
// 商品的尺码列表
_.get(cur, 'size_list', []).forEach((size) => {
if (data.attribute === 3) {
// 虚拟商品,门票默认最大为4,
size.storage_number = size.storage_number > 4 ? 4 : size.storage_number;
}
// 如果status为0,即skc下架时就跳过该商品
if (cur.status === 0) {
size.storage_number = 0;
}
goodsGroup.size.push({
name: size.size_name,
sku: size.product_sku,
num: _.parseInt(size.storage_number),
goodsId: size.size_id
});
// 单个sku商品的总数
goodsGroup.total += _.parseInt(size.storage_number);
if (goodsGroup.total > 0 && !chooseSkuFlag) { // 默认选中该sku商品
goodsGroup.focus = true;
chooseSkuFlag = true;// 选中sku商品
}
totalStorageNum += _.parseInt(size.storage_number);
});
acc.push(goodsGroup);
return acc;
}, []);
if (!_.isEmpty(skuGoods) && !chooseSkuFlag) { // 没有选中一个sku商品,默认选中第一个sku商品
_.head(skuGoods).focus = true;
}
return {
defaultImage: defaultImage,
skuGoods: skuGoods,
totalStorageNum: totalStorageNum
};
};
/**
* 使sizeBoList id以 sizeAttributeBos id顺序一样
* @param sizeInfoBo
*/
const _sizeInfoBoSort = (sizeInfoBo) => {
if (!sizeInfoBo.sizeBoList || !sizeInfoBo.sizeAttributeBos) {
return {};
}
_.get(sizeInfoBo, 'sizeBoList', []).forEach((sizeBoList, sizek)=> {
let sortAttr = {};
sizeBoList.sortAttributes.forEach(sortAttributes => {
sortAttr[sortAttributes.id] = sortAttributes;
});
sizeInfoBo.sizeBoList[sizek].sortAttributes = sortAttr;
});
_.get(sizeInfoBo, 'sizeBoList', []).forEach((sizeBoList, sizek)=> {
let sortAttr = [];
sizeInfoBo.sizeAttributeBos.forEach(val => {
if (sizeBoList.sortAttributes[val.id]) {
sortAttr.push(sizeBoList.sortAttributes[val.id]);
}
});
sizeInfoBo.sizeBoList[sizek].sortAttributes = sortAttr;
});
return sizeInfoBo;
};
/**
* 获取尺寸信息
* @param sizeInfo
* @returns {{}}
*/
const _getSizeData = (sizeInfo) => {
// 尺码信息
if (!_.has(sizeInfo, 'sizeInfoBo')) {
return {};
}
sizeInfo.sizeInfoBo = _sizeInfoBoSort(sizeInfo.sizeInfoBo);
let boyReference = _.get(sizeInfo, 'productExtra.boyReference', false);
let girlReference = _.get(sizeInfo, 'productExtra.girlReference', false);
let gender = _.get(sizeInfo, 'productDescBo.gender', 3);
let referenceName = (function() {
if (gender === 3 && boyReference) {
return '参考尺码(男)';
} else if (gender === 3 && girlReference) {
return '参考尺码(女)';
} else {
return '参考尺码';
}
}());
// 判断是否显示参考尺码
let showReference = (boyReference && _.get(sizeInfo, 'sizeInfoBo.sizeBoList[0].boyReferSize', false)) ||
(girlReference && _.get(sizeInfo, 'sizeInfoBo.sizeBoList[0].girlReferSize', false));
if (!_.has(sizeInfo, 'sizeInfoBo.sizeAttributeBos')) {
return {};
}
// 尺码信息头部
let size = {
thead: [{name: '吊牌尺码', id: ''}],
tbody: []
};
// 显示参考尺码
if (showReference) {
size.thead[1] = {name: referenceName, id: ''};
}
_.get(sizeInfo, 'sizeInfoBo.sizeAttributeBos', []).forEach((value) => {
size.thead.push({
name: value.attributeName || ' ',
id: value.id
});
});
_.get(sizeInfo, 'sizeInfoBo.sizeBoList', []).forEach((value) => {
let sizes = [];
// 吊牌尺码
sizes.push(value.sizeName);
// 判断是否显示参考尺码
if (boyReference && (gender === 1 || gender === 3) && showReference) {
sizes.push(_.get(value, 'boyReferSize.referenceName', ' '));
} else if (girlReference && (gender === 2 || gender === 3) && showReference) {
sizes.push(_.get(value, 'girlReferSize.referenceName', ' '));
} else {
if (size.thead[1] && showReference) {
size.thead[1] = {};
}
}
// 其他尺码信息
_.get(value, 'sortAttributes', []).forEach(attr => {
sizes.push(_.get(attr, 'sizeValue', ' '));
});
// 尺码信息
size.tbody.push(sizes);
});
// 参考尺码为空
if (_.isEmpty(size.thead[1]) && showReference) {
// 移除这个值
size.thead.splice(1, 1);
}
// 测量方式
if (sizeInfo.sizeImage) {
size.sizeImg = sizeInfo.sizeImage;
}
return size;
};
/**
* 商品尺码信息
*
* @param productSkn
* @param maxSortId
* @return object
*/
const _getIntroInfo = (productSkn, additionalData)=> {
if (!productSkn) {
return {};
}
let sizeInfo = additionalData.sizeInfo;
if (_.isEmpty(sizeInfo)) {
return {};
}
let result = {};
// 尺寸数据
result.size = _getSizeData(sizeInfo);
return result;
};
/**
* 详情页数据格式化
* @param origin Object 原始数据
* @return result Object 格式化数据
*/
const _detailDataPkg = (origin) => {
return co(function*() {
if (_.isEmpty(origin) || _.isEmpty(origin)) {
return {};
}
let result = {};
let propOrigin = _.partial(_.get, origin);
// 商品名称
if (!propOrigin('product_name')) {
return result;
}
result.name = propOrigin('product_name');
result.skn = propOrigin('product_skn');
result.productId = propOrigin('product_id');
// 商品价格
result.marketPrice = propOrigin('format_market_price');
result.salePrice = propOrigin('format_sales_price');
result.hasOtherPrice = true;
if (result.salePrice === '0') {
delete result.salePrice;
result.hasOtherPrice = false;
}
// 上市期
if (propOrigin('expect_arrival_time')) {
result.arrivalDate = `${propOrigin('expect_arrival_time')}月`;
result.presalePrice = propOrigin('format_sales_price');
delete result.salePrice;
result.hasOtherPrice = false;
}
// sku商品信息
let skuData = _getSkuDataByProductBaseInfo(origin);
// 商品购买状态
let soldOut = !!(propOrigin('status') === 0 || skuData.totalStorageNum === 0);
let notForSale = propOrigin('attribute') === 2; // 非卖品
let virtualGoods = propOrigin('attribute') === 3; // 虚拟商品
if (!soldOut && !notForSale && !virtualGoods) {
result.addToCart = 1;
}
result.colors = skuData.skuGoods;
return result;
})();
};
/**
* 获取某一个商品详情主页面
*/
const getProductInfoAsync = (pid) => {
return co(function * () {
if (!pid) {
return {};
}
// 获取商品基本信息
let productData = yield productAPI.getProductAsync(pid);
if (_.isEmpty(productData.data)) {
return Promise.reject({
code: 404,
message: 'app.product.data api wrong'
});
}
let productSkn = _.get(productData, 'data.product_skn');
let requestData = yield Promise.all([
_getProductIntroAsync(productSkn), // 商品详细介绍
_detailDataPkg(productData.data) // 商品详细价格
]);
let productDescription = requestData[0];
let productInfo = requestData[1];
let intro = _getIntroInfo(productSkn, productDescription);
return Object.assign(productInfo, intro);
})();
};
module.exports = {
getProductInfoAsync // 获取某一个商品详情主页面
};
... ...
/**
* Created by TaoHuang on 2016/10/19.
*/
const api = global.yoho.API;
const sizeInfoAsync = skn => {
return api.get('', {
method: 'h5.product.intro',
productskn: skn
});
};
/**
* 获得产品信息
* @param pid
* @returns {Promise.<type>}
*/
const getProductAsync = (pid) => {
return api.get('', {
method: 'app.product.data',
product_id: pid
});
};
module.exports = {
sizeInfoAsync,
getProductAsync
};
... ...
/**
* router of sub app cart
* @author: htoooth<ht.anglenx@gmail.com>
* @date: 2016/10/19
*/
'use strict';
const router = require('express').Router(); // eslint-disable-line
const cRoot = './controllers';
const cart = require(`${cRoot}/cart`);
router.get('/index/getProductInfo', cart.getProductInfo);
// Your controller here
module.exports = router;
... ...
<div class="detail-header">
<span class="colse">X关闭</span>
</div>
<div class="detail-body">
<span class="magnify"></span>
{{#colors}}
<div class="detail-bigpic {{#unless focus}}none{{/unless}}">
{{#thumbs}}
<div class="bigpic">
<img src="{{shower}}">
</div>
{{/thumbs}}
<div class="piclist">
<span class="pre"></span>
<div class="con">
<ul>
{{#thumbs}}
<li><img src="{{img}}"></li>
{{/thumbs}}
</ul>
</div>
<span class="next"></span>
</div>
</div>
{{/colors}}
<div class="detail-info">
<div class="title">
<h2>{{name}}</h2>
</div>
<div class="type">
<span class="type-s">新品</span>
</div>
<div class="price">
{{#if salePrice}}
<span class="oldprice">原价:<del>¥{{marketPrice}}</del></span>
<span class="newprice">现价:<b class="promotion-price">¥{{salePrice}}</b></span>
{{^}}
<span class="newprice {{#presalePrice}}none{{/presalePrice}}">原价:<b class="promotion-price">¥{{marketPrice}}</b></span>
{{/if}}
{{#if presalePrice}}
<span class="oldprice">原价:<del>¥{{marketPrice}}</del></span>
<span class="newprice">预售价:<b class="promotion-price">¥{{presalePrice}}</b></span>
{{/if}}
{{#arrivalDate}}
<span class="arrivalDate">上市期:{{arrivalDate}}</span>
{{/arrivalDate}}
</div>
<div class="order">
<dl>
<dd class="colorBox">选颜色:</dd>
<dt>
<div class="colorBox">
<ul>
{{#colors}}
<li class="color">
<p class="{{#if focus}}atcive{{/if}}"><span></span><img src="{{src}}"></p>
<span>{{name}}</span>
</li>
{{/colors}}
</ul>
</div>
</dt>
<dd class="">选尺码:</dd>
<dt>
{{#colors}}
<div class="showSizeBox {{#unless focus}}none{{/unless}}">
{{#size}}
<span data-sku="{{sku}}" data-num="{{num}}">{{name}}</span>
{{/size}}
</div>
{{/colors}}
</dt>
<dd>选件数:</dd>
<dt>
<div class="amount_wrapper">
<i class="amount cut"></i>
<input type="text" id="mnum" class="mnum" value="1" readonly="readonly">
<i class="amount add"></i>
</div>
</dt>
</dl>
</div>
<div class="submit">
<input class="addcart" type="button">
<input class="btn_pre_sale none" type="button">
<input class="btn_sellout none" type="button">
<input class="fav_count" type="button">
</div>
</div>
<div class="detail-size">
<h3>尺码信息<span>(单位:厘米)</span></h3>
{{# size}}
<table>
<thead>
<tr>
{{# thead}}
<td width="{{width}}">{{name}}</td>
{{/ thead}}
</tr>
</thead>
<tbody>
{{# tbody}}
<tr>
{{#each .}}
<td>{{.}}</td>
{{/each}}
</tr>
{{/ tbody}}
</tbody>
</table>
{{/ size}}
<div class="size-info">
※ 以上尺寸为实物实际测量,因测量方式不同会有略微误差,相关数据仅作参考,以收到实物为准。
</div>
</div>
</div>
<input value="{{addToCart}}" id="addToCart" type="hidden" />
... ...
... ... @@ -178,7 +178,7 @@ const local = {
return `${config.siteUrl}/home`;
}
}());
console.log(user.uid);
AuthHelper.syncUserSession(user.uid, req, res).then(() => {
res.json({
code: 200,
... ...
... ... @@ -16,4 +16,5 @@ module.exports = app => {
app.use('/home', require('./apps/home')); // 会员中心
app.use('/brands', require('./apps/brands'));
app.use('/guang', require('./apps/guang'));
app.use('/cart', require('./apps/cart'));// 购物车
};
... ...