Authored by htoooth

refactor

Showing 64 changed files with 2441 additions and 932 deletions

Too many changes to show.

To preserve performance only 64 of 64+ files are displayed.

**/bundle/**/*.js
**/dist/**/*.js
coverage
... ...
**/css/**/*.css
**/dist/**/*.css
public/scss/common/_slider.css
... ...
... ... @@ -17,18 +17,19 @@ const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const favicon = require('serve-favicon');
const session = require('yoho-express-session');
const memcached = require('yoho-connect-memcached');
const _ = require('lodash');
const session = require('express-session');
const memcached = require('connect-memcached');
const hbs = require('express-handlebars');
const pkg = require('./package.json');
const cookie = require('./library/cookie');
const yohoLib = require('yoho-node-lib');
const app = express();
const MemcachedStore = memcached(session);
const seo = require('./doraemon/middleware/seo');
// 指定libray目录
global.library = path.resolve('./library');
// 全局注册library
yohoLib.global(config);
global.middleware = path.resolve('./doraemon/middleware');
global.utils = path.resolve('./utils');
... ... @@ -36,8 +37,15 @@ global.utils = path.resolve('./utils');
app.locals.devEnv = app.get('env') === 'development';
app.locals.version = pkg.version;
app.set('view engine', '.hbs');
app.set('views', './doraemon/views');
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: './doraemon/views',
partialsDir: './doraemon/views/partial',
helpers: global.yoho.helpers
}));
app.use(favicon(path.join(__dirname, '/public/favicon.ico')));
app.use(express.static(path.join(__dirname, 'public')));
... ... @@ -63,26 +71,41 @@ app.use(session({
}));
app.use((req, res, next) => {
req.user = {};
// 从 PHP 写的 SESSION 中获取到当前登录用户的 UID
if (req.session && _.isNumber(req.session._LOGIN_UID)) {
req.user.uid = req.session._LOGIN_UID;
}
req.user = {}; // 全局的用户数据
req.yoho = {}; // req和res绑定yoho对象,用于传递全局数据, 如req.yoho.channel等
// session 没有读取到的时候,从 cookie 读取 UID
if (!req.user.uid && req.cookies._UID) {
req.user.uid = cookie.getUid(req);
}
next();
});
app.use(seo());
const logger = global.yoho.logger;
// dispatcher
require('./dispatch')(app);
try {
const mobileCheck = require('./doraemon/middleware/mobile-check');
const user = require('./doraemon/middleware/user');
const seo = require('./doraemon/middleware/seo');
const setYohoData = require('./doraemon/middleware/set-yoho-data');
const errorHanlder = require('./doraemon/middleware/error-handler');
const setPageInfo = require('./doraemon/middleware/set-pageinfo');
// YOHO 前置中间件
app.use(mobileCheck());
app.use(setYohoData());
app.use(user());
app.use(seo());
app.use(setPageInfo());
require('./dispatch')(app);
app.all('*', errorHanlder.notFound()); // 404
// YOHO 后置中间件
app.use(errorHanlder.serverError());
} catch (err) {
logger.error(err);
}
// listener
app.listen(config.port, function() {
console.log('yohobuy start');
logger.info('yohobuy start');
});
... ...
... ... @@ -23,7 +23,7 @@ app.engine('.hbs', hbs({
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: require('../../library/helpers')
helpers: global.yoho.helpers
}));
// router
... ...
... ... @@ -5,17 +5,17 @@
*/
'use strict';
const ServiceAPI = require(`${global.library}/api`).ServiceAPI;
const sign = require(`${global.library}/sign`);
const logger = require(`${global.library}/logger`);
const logger = global.yoho.logger;
const headerModel = require('../../../doraemon/models/header');
var api = new ServiceAPI();
var api = global.yoho.ServiceAPI;
const getstaticFile = (id) => {
return api.get('staticFileManage/queryById', sign.apiSign({
const _getstaticFile = (id) => {
return api.get('activity/staticFileManage/queryById', {
id: id
}), true).then(result => {
}, {
cache: true
}).then(result => {
if (result && result.code === 200) {
result.data.title = result.data.pageTitle;
result.data.keywords = result.data.keyWord;
... ... @@ -29,5 +29,5 @@ const getstaticFile = (id) => {
};
exports.getSpecialData = (id, type) => {
return Promise.all([headerModel.requestHeaderData(type), getstaticFile(id)]);
return Promise.all([headerModel.requestHeaderData(type), _getstaticFile(id)]);
};
... ...
... ... @@ -21,15 +21,15 @@ exports.index = (req, res, next) => {
}).catch(next);
};
exports.getbrandFloorDataAjax = (req, res) => {
exports.getbrandFloorDataAjax = (req, res, next) => {
const channelType = req.query.channelType || 'boys';
channelModel.getbrandFloorDataAjax(channelType).then(data => {
res.json(data);
});
}).catch(next);
};
exports.getNewArrival = (req, res) => {
exports.getNewArrival = (req, res, next) => {
let reqBody = req.body,
pageIndex = reqBody.pageIndex,
pageCount = reqBody.pageCount,
... ... @@ -54,5 +54,5 @@ exports.getNewArrival = (req, res) => {
};
}
res.send(result);
});
}).catch(next);
};
... ...
... ... @@ -26,7 +26,7 @@ app.engine('.hbs', hbs({
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [`${partials}/partials`, `${doraemon}/partial`],
helpers: require('../../library/helpers')
helpers: global.yoho.helpers
}));
... ...
/**
* girls model
* 频道页 model
* @author: 赵彪<bill.zhao@yoho.cn>
* @date: 2016/05/17
*/
... ... @@ -7,31 +7,30 @@
const _ = require('lodash');
const ServiceAPI = require(`${global.library}/api`).ServiceAPI;
const SearchAPI = require(`${global.library}/api`).SearchAPI;
const sign = require(`${global.library}/sign`);
const helpers = require(`${global.library}/helpers`);
const images = require(`${global.utils}/images`);
const log = require(`${global.library}/logger`);
const dataMap = require('../../../config/data-map');
const helpers = global.yoho.helpers;
const log = global.yoho.logger;
const serviceApi = new ServiceAPI();
const searchApi = new SearchAPI();
const processProduct = require(`${global.utils}/product-process`).processProduct;
const serviceApi = global.yoho.ServiceAPI;
const searchApi = global.yoho.SearchAPI;
const headerModel = require('../../../doraemon/models/header');
const getShelveTime = duration => {
// 获取上线时间
const _getShelveTime = duration => {
let today = new Date(),
todayMil = today.getTime().toString().substr(0, 10),
startDayMil = (today.setMonth(today.getMonth() - duration)).toString().substr(0, 10);
return `${startDayMil},${todayMil}`;
// `
return `${startDayMil},${todayMil}`; // `返回时间
};
// 获取可用的标题
const getText = data => {
const _getText = data => {
let text = data.split(' ')[0];
const regResult = /\w+/.exec(text);
... ... @@ -46,257 +45,21 @@ const getText = data => {
return text;
};
const channelMap = {
boys: {
code: '79372627eee75d73afe7f9bac91e5ce6',
gender: '1,3'
},
girls: {
code: '75215008957605c05e8cd375eac4f817',
gender: '2,3'
},
kids: {
code: 'd71f4b27f2a7229fbb31a4bc490a6f36',
gender: '2,3'
},
lifestyle: {
code: '8a341ca7eacc069ba80f02dec80eaf34',
// code: '380c38155fd8beee10913a3f5b462da6',
// code: '665f7c2fb9d037ee820766953ee34bf7',
gender: '2,3'
}
};
const sortMap = {
boys: [
{sort: 147, viewNum: 5}, // 休闲运动鞋
{sort: 129, viewNum: 5}, // 休闲裤
{sort: 152, viewNum: 5}, // 双肩包
{misort: 11, viewNum: 5}, // T恤
{sort: 115, viewNum: 5}, // 衬衫
{sort: 130, viewNum: 5}, // 牛仔裤
{misort: 60, viewNum: 5}, // 帽子
{sort: 124, viewNum: 5}, // 夹克
{sort: 119, viewNum: 5}, // 卫衣
{sort: 162, viewNum: 5}, // 手表
{sort: 148, viewNum: 5}, // 靴子
{misort: 65, viewNum: 5}, // 首饰
{sort: 151, viewNum: 5}, // 时装鞋
{misort: 61, viewNum: 5}, // 太阳镜
{misort: 39, viewNum: 5}, // 袜子
{sort: 346, viewNum: 5}, // 运动裤
{sort: 131, viewNum: 5}, // 短裤
{misort: 66, viewNum: 5}, // 配饰
{misort: 309, viewNum: 5}, // 内裤
{misort: 30, viewNum: 5}, // 打底裤/紧身裤
{sort: 342, viewNum: 5} // 邮差包
],
girls: [
{misort: 16, viewNum: 4}, // 卫衣
{misort: 12, viewNum: 4}, // 衬衫
{misort: 44, viewNum: 4}, // 休闲/运动鞋
{misort: 11, viewNum: 4}, // T恤
{misort: 21, viewNum: 4}, // 夹克
{misort: 257, viewNum: 4}, // 毛衣/针织
{misort: 22, viewNum: 4}, // 大衣/风衣
{misort: 26, viewNum: 4}, // 休闲裤
{misort: 27, viewNum: 4}, // 牛仔裤
{misort: 31, viewNum: 4}, // 连衣裙
{misort: 32, viewNum: 4}, // 半身裙
{misort: 48, viewNum: 4}, // 时装鞋
{misort: 49, viewNum: 4}, // 双肩包
{misort: 50, viewNum: 4}, // 手拎包/单肩包
{misort: 60, viewNum: 4}, // 帽子
{misort: 65, viewNum: 4}, // 首饰
{misort: 59, viewNum: 4}, // 手表
{misort: 61, viewNum: 4}, // 太阳镜
{misort: 66, viewNum: 4} // 配饰
],
kids: [
{misort: 366, viewNum: 4}, // T恤
{misort: 367, viewNum: 4}, // 衬衫
{misort: 396, viewNum: 4}, // 卫衣
{misort: 400, viewNum: 4}, // // 毛衣/针织
{misort: 404, viewNum: 4}, // 夹克
{misort: 369, viewNum: 4}, // 休闲裤
{misort: 388, viewNum: 4}, // 牛仔裤
{misort: 371, viewNum: 4}, // 连衣裙
{misort: 370, viewNum: 4}, // 半身裙
{misort: 368, viewNum: 4}, // 休闲/运动鞋
{misort: 392, viewNum: 4}, // 双肩包
{misort: 414, viewNum: 4}, // 帽子
{misort: 372, viewNum: 4}, // 短裤
{misort: 384, viewNum: 4}, // 打底裤/紧身裤
{misort: 382, viewNum: 4}, // 凉鞋/拖鞋
{misort: 402, viewNum: 4}, // 马甲
{misort: 386, viewNum: 4}, // 背心
{misort: 406, viewNum: 4}, // 大衣/风衣
{misort: 430, viewNum: 4}, // 羽绒服
{misort: 423, viewNum: 4}, // 棉衣
{misort: 417, viewNum: 4} // 套装
],
lifestyle: [
{sort: 171, viewNum: 5}, // 耳机
{sort: 398, viewNum: 5}, // 只能装备
{sort: 185, viewNum: 5}, // 相机
{misort: 259, viewNum: 5}, // 美妆
{sort: 267, viewNum: 5}, // 杯子/水壶
{sort: 313, viewNum: 5}, // 手机/ipad壳套
{sort: 211, viewNum: 5}, // 数码配件
{sort: 292, viewNum: 5}, // 玩偶
{sort: 272, viewNum: 5}, // 储物收纳
{sort: 183, viewNum: 5}, // 启用家居
{sort: 273, viewNum: 5}, // 厨具/餐具
{sort: 271, viewNum: 5} // 靠枕/靠垫/抱枕
]
};
const getNavs = rawNavs => {
const navs = rawNavs;
let list = [];
_.forEach(navs, it => {
let obj = {};
obj.name = it.name;
obj.href = it.url;
list.push(obj);
});
const channelMap = dataMap.channel;
return list;
};
const sortMap = dataMap.sort;
// 构建url
const httpBuildQuery = data => {
const _httpBuildQuery = data => {
return searchApi.get('/search.json', data);
};
/**
* 格式化商品信息
*
* @param array $productData 需要格式化的商品数据
* @param bool $showTags 控制是否显示标签
* @param bool $showNew 控制是否显示NEW图标
* @param bool $showSale 控制是否显示SALE图标
* @param int $width 图片的宽度
* @param int $height 图片的高度
* @param bool $isApp 判断是不是APP访问
* @param bool $showPoint 商品价格是否显示小数位,默认显示
* @return array | false
* 获取带小图的banner
* @param {[Object]} data 原始数据
* @return {[Object]} 转换后的数据
*/
const formatProduct = (productData, showTags, showNew, showSale, width, height, isApp, showPoint) => {
let result = {};
// 默认值
if (!showTags) {
showTags = true;
}
if (!showNew) {
showNew = true;
}
if (!showSale) {
showSale = true;
}
if (!width) {
width = 290;
}
if (!height) {
height = 388;
}
if (!isApp) {
isApp = false;
}
if (!showPoint) {
showPoint = true;
}
// 商品信息有问题,则不显示
if (!productData.product_skn || !productData.goods_list[0]) {
return false;
}
// 市场价和售价一样,则不显示市场价
if (parseInt(productData.market_price, 0) === parseInt(productData.sales_price, 0)) {
productData.market_price = false;
}
// 设置默认图片
_.forEach(productData.goods_list, item => {
if (item.is_default === 'Y') {
productData.default_images = item.images_url;
}
});
if (!productData.default_images) {
productData.default_images = productData.goods_list[0].images_url;
}
result.id = productData.product_skn;
result.product_id = productData.product_id;
result.thumb = images.getImageUrl(productData.default_images, width, height);
result.name = productData.product_name;
result.price = !productData.market_price ? false : productData.market_price;
result.salePrice = productData.sales_price;
if (showPoint) {
result.price += '.00';
result.salePrice += '.00';
}
result.is_soon_sold_out = (productData.is_soon_sold_out === 'Y');
result.url = 'http://item.yohobuy.com/product/pro_' +
productData.product_id + '_' +
productData.goods_list[0].goods_id + '/' +
productData.cn_alphabet + '.html';
// APP访问需要加附加的参数
// 备注:如果以后APP的接口太多,可以把这边参数提取出来,变成一个公共的方法来生成,便于以后管理维护
if (isApp) {
result.url += '?openby:yohobuy={"action":"go.productDetail","params":{"product_skn":' +
productData.product_skn + '}}';
}
if (showTags) {
result.tags = {};
result.tags.is_new = showNew && productData.is_new && productData.is_new === 'Y'; // 新品
result.tags.is_discount = showSale && productData.is_discount && productData.is_discount === 'Y'; // 在售
result.tags.is_limited = productData.is_limited && productData.is_limited === 'Y'; // 限量
result.tags.is_yohood = productData.is_yohood && productData.is_yohood === 'Y'; // YOHOOD
result.tags.midYear = productData['mid-year'] && productData['mid-year'] === 'Y'; // 年中
result.tags.yearEnd = productData['year-end'] && productData['year-end'] === 'Y'; // 年末
result.tags.is_advance = productData.is_advance && productData.is_advance === 'Y'; // 再到着
if (result.is_soon_sold_out && result.tags.is_discount) {
result.tags.is_new = false;// 打折与即将售完组合显示打折
} else if (result.tags.is_discount &&
(result.tags.is_new || result.tags.is_limited || result.tags.is_yohood || result.tags.is_advance)) {
result.tags.is_discount = false;// 打折与其它组合则隐藏打折
} else if (result.tags.is_yohood && result.tags.is_new) {
result.tags.is_new = false;// YOHOOD和新品组合显示YOHOOD
}
}
return result;
};
const getBannerList = data => {
let list = [];
_.forEach(data, (bannerData) => {
let obj = {};
obj.href = bannerData.url;
obj.img = bannerData.src;
obj.name = bannerData.title;
list.push(obj);
});
return list;
};
const getDebrisSlide = data => {
const _getDebrisSlide = data => {
let floorData = {
debrisSlider: {
left: [],
... ... @@ -307,35 +70,32 @@ const getDebrisSlide = data => {
_.mapKeys(data, (value, key) => {
_.forEach(value, slideData => {
let obj = {};
obj.href = slideData.url;
obj.img = slideData.img;
floorData.debrisSlider[key].push(obj);
floorData.debrisSlider[key].push(slideData);
});
});
return floorData;
};
const getadbannerData = data => {
const obj = {
adbanner: {
href: '',
img: '',
name: ''
}
};
obj.adbanner.href = data.url;
obj.adbanner.img = data.src;
obj.adbanner.name = data.title;
/**
* 获取广告位
* @param {Object} data 原始数据
* @return {Object} 转换后的数据
*/
const _getadbannerData = data => {
return obj;
return {
adbanner: data
};
};
const getSlideData = srcData => {
/**
* 生成banner模板数据
* @param {Object} srcData 原始数据
* @return {Object} 转换后的数据
*/
const _getSlideData = srcData => {
const slideData = {
slide: {
list: [],
... ... @@ -344,22 +104,27 @@ const getSlideData = srcData => {
};
if (srcData.big_image) {
slideData.slide.list = getBannerList(srcData.big_image);
slideData.slide.list = srcData.big_image;
}
if (srcData.list) {
slideData.slide.pagination = getBannerList(srcData.list);
slideData.slide.pagination = srcData.list;
}
if (_.isArray(srcData)) {
slideData.slide.list = getBannerList(srcData);
slideData.slide.list = srcData;
}
return slideData;
};
const getNewReportFloorData = args => {
/**
* 获取最新速报模板数据
* @param {Object} srcData 原始数据
* @return {Object} 转换后的数据
*/
const _getNewReportFloorData = args => {
const title = args[0].data.text;
let item = args[1].data;
let secondItem = args[2].data;
... ... @@ -367,7 +132,6 @@ const getNewReportFloorData = args => {
let forthItem = args[4];
let list = [];
let obj = {};
const data = {
newReport: {
... ... @@ -381,43 +145,44 @@ const getNewReportFloorData = args => {
let floorDatas = [];
obj.href = item[0].url;
obj.img = item[0].src;
list.push(obj);
list.push(item[0]);
_.forEach(secondItem, (floorData) => {
let o = {};
o.href = floorData.url;
o.img = floorData.src;
list.push(o);
list.push(floorData);
});
obj.href = thirdItem[0].url;
obj.img = thirdItem[0].src;
list.push(obj);
list.push(thirdItem[0]);
data.newReport.list = list;
floorDatas.push(data);
if (forthItem.template_name === 'single_image') {
adData = getadbannerData(forthItem.data[0]);
adData = _getadbannerData(forthItem.data[0]);
floorDatas.push(adData);
}
return floorDatas;
};
const setChannelType = (obj, type) => {
/**
* 给目标对象绑定频道属性
* @param {Object} obj 需要绑定频道属性的对象
* @param {String} type 需要设置的频道类型
* @return undefined
*/
const _setChannelType = (obj, type) => {
obj[type + 'Channel'] = true;
};
// 优选品牌
const getPreBrandTopData = (args, type) => {
/**
* 获取优选品牌模板数据
* @param {[Object]} args 参数列表
* @param {String} type 频道类型
* @return {Object}
*/
const _getPreBrandTopData = (args, type) => {
const title = args[0].data.text;
let item = args[1].data;
... ... @@ -429,22 +194,20 @@ const getPreBrandTopData = (args, type) => {
}
};
_.forEach(item, (topData) => {
let o = {};
data.preferenceBrands.imgBrand = item;
o.href = topData.url;
o.img = topData.src;
data.preferenceBrands.imgBrand.push(o);
});
setChannelType(data.preferenceBrands, type);
_setChannelType(data.preferenceBrands, type);
return data;
};
// 热门品类
const getHotGoodsFloorData = (args, type) => {
/**
* 获取热门分类模板数据
* @param {[Object]} args 参数列表
* @param {String} type 频道类型
* @return {Object}
*/
const _getHotGoodsFloorData = (args, type) => {
let item = args[0];
let nextItem = args[1];
... ... @@ -463,67 +226,46 @@ const getHotGoodsFloorData = (args, type) => {
}
};
_.forEach(item.data.menuNav.list, it => {
let obj = {};
obj.name = it.name;
obj.href = it.url;
category.push(obj);
});
_.forEach(item.data.menuNav.blocks, it => {
let obj = {};
category = item.data.menuNav.list;
obj.name = it.title;
obj.href = it.url;
obj.img = it.img;
keyword.push(obj);
});
keyword = item.data.menuNav.blocks;
_.forEach(item.data.imgs, (it, idx) => {
let obj = {};
obj.name = it.title;
obj.href = it.url;
obj.img = it.img;
if (idx === 0 || (idx === 4 && type === 'boys')) {
brands.push(obj);
brands.push(it);
} else {
types.push(obj);
types.push(it);
}
});
_.forEach(nextItem.data, (it) => {
let obj = {};
obj.name = it.title;
obj.href = it.url;
obj.img = it.src;
products.push(obj);
});
products = nextItem.data;
object.name = item.data.name;
object.keyword = keyword;
object.category = category;
object.brands = brands;
object.types = types;
object.navs = getNavs(item.data.navs.list);
object.navs = item.data.navs.list;
object.products = products;
list.push(object);
data.recommend.tplrecommend = list;
setChannelType(data.recommend, type);
_setChannelType(data.recommend, type);
return data;
};
const getBoysSingleHot = (args, type) => {
/**
* 获取boys人气单品模版数据
* @param {[Object]} args 参数列表
* @param {String} type 频道类型
* @return {Object}
*/
const _getBoysSingleHot = (args, type) => {
const len = 10;
const data = {
singlehot: {
... ... @@ -539,7 +281,6 @@ const getBoysSingleHot = (args, type) => {
for (let i = 0; i < len; i++) {
let pos = i;
let val = {};
let obj = {};
if (i === 1) {
val = args[1].data[0]; // 第二个是大图
... ... @@ -551,24 +292,28 @@ const getBoysSingleHot = (args, type) => {
}
val = args[2].data[pos];
}
obj.href = val.url;
obj.img = val.src;
list.push(obj);
list.push(val);
}
data.singlehot.imgHot = list;
setChannelType(data.singlehot, type);
_setChannelType(data.singlehot, type);
floorDatas.push(data);
if (args[3].template_name === 'single_image') {
adData = getadbannerData(args[3].data[0]);
adData = _getadbannerData(args[3].data[0]);
floorDatas.push(adData);
}
return floorDatas;
};
const getGirlsSingleHot = args => {
/**
* 获取girls人气单品模版数据
* @param {[Object]} args 参数列表
* @param {String} type 频道类型
* @return {Object}
*/
const _getGirlsSingleHot = args => {
let goods = args[2].data;
let skns = '';
... ... @@ -591,16 +336,28 @@ const getGirlsSingleHot = args => {
};
// 人气单品
const getSingleHotFloorData = (args, type) => {
/**
* 人气单品入口
* @param {[Object]} args 参数列表
* @param {String} type 频道类型
* @return {Object}
*/
const _getSingleHotFloorData = (args, type) => {
if (type === 'boys') {
return getBoysSingleHot(args, type);
return _getBoysSingleHot(args, type);
} else {
return getGirlsSingleHot(args, type);
return _getGirlsSingleHot(args, type);
}
};
const getAsyncSingleHot = (args, queryResult, type) => {
/**
* 处理异步获取的人气单品数据
* @param {[Object]} args 参数列表
* @param {Object} queryResult 异步获取的数据
* @param {String} type 频道类型
* @return {Object}
*/
const _getSingehotViaResult = (args, queryResult, type) => {
const data = {
singlehot: {
name: args[0].data.text,
... ... @@ -611,51 +368,49 @@ const getAsyncSingleHot = (args, queryResult, type) => {
};
if (args[3].template_name === 'app_icon_list') {
_.forEach(args[3].data, it => {
let obj = {};
obj.href = it.url;
obj.name = it.title;
obj.img = it.src;
data.singlehot.brands.push(obj);
});
data.singlehot.brands = args[3].data;
}
_.forEach(queryResult.data.product_list, (it, index) => {
let obj = {};
const formatData = formatProduct(it, true, true, true, 280, 373);
if (index > 12) {
return;
}
obj.price = formatData.salePrice;
obj.href = formatData.url;
obj.img = formatData.thumb;
obj.name = formatData.name;
if (queryResult.data) {
_.forEach(queryResult.data.product_list || [], (it, index) => {
const formatData = processProduct(it, {
width: 280,
height: 373
});
if (index < 3) {
obj.tip = 'TOP' + (index + 1);
}
if (index > 12) {
return;
}
data.singlehot.imgHot.push(obj);
});
if (index < 3) {
formatData.tip = 'TOP' + (index + 1);
}
data.singlehot.navs = getNavs(args[1].data);
setChannelType(data.singlehot, type);
data.singlehot.imgHot.push(formatData);
});
}
data.singlehot.navs = args[1].data;
_setChannelType(data.singlehot, type);
return data;
};
const processFloorDataWithQueryReusult = (rawData, floorData, queryResult, title, type) => {
/**
* 异步获取人气单品
* @param {[Object]} rawData 接口返回的原始数据
* @param {[Object]} floorData 已经经过处理的楼层数据
* @param {Object} queryResult 接口中用于请求人气单品的数据
* @param {String} title 人气单品楼层的标题
* @param {Type} type 人气单品楼层的类型
* @return {Object}
*/
const _processFloorDataWithQueryReusult = (rawData, queryResult, title, type) => {
let data = {};
_.forEach(rawData, (subData, index) => {
const text = subData.data.text && getText(subData.data.text);
const text = subData.data.text && _getText(subData.data.text);
if (text === title) {
data = getAsyncSingleHot(rawData.slice(index, index + 4), queryResult, type);
data = _getSingehotViaResult(rawData.slice(index, index + 4), queryResult, type);
}
});
... ... @@ -668,7 +423,7 @@ const processFloorDataWithQueryReusult = (rawData, floorData, queryResult, title
* @param string data
* return obj
*/
const getNewGoodsFloorData = args => {
const _getNewGoodsFloorData = args => {
const data = {
newArrivls: {
name: args[0].data.text,
... ... @@ -676,157 +431,98 @@ const getNewGoodsFloorData = args => {
}
};
data.newArrivls.navs = getNavs(args[1].data);
data.newArrivls.navs = args[1].data;
return data;
};
/**
* 获取最新上架商品数据
*
* @param string $channel
* @return array
*/
exports.getNewArrival = channel => {
let rel = [],
sortList = sortMap[channel],
params = {
order: 'shelve_time:desc',
status: 1,
sales: 'Y',
attribute_not: 2,
stocknumber: 3,
shelve_time: getShelveTime(20)
};
params.gender = channelMap[channel].gender;
_.forEach(sortList, (item) => {
let data = Object.assign(item, params);
rel.push(httpBuildQuery(data));
});
return Promise.all(rel).then(res => {
let data = [],
result = [];
_.forEach(sortList, (it, index) => {
if (res[index].data.product_list.length === sortList[index].viewNum) {
data = data.concat(res[index].data.product_list);
}
});
_.forEach(data, (item) => {
result.push(formatProduct(item, true, true, true, 280, 373));
});
return result;
});
};
const getCategoryFloorData = args => {
const _getCategoryFloorData = args => {
const data = {
category: {
name: args[0].data.text,
navs: getNavs(args[1].data),
navs: args[1].data,
list: []
}
};
_.forEach(args[2].data, (it, index) => {
let obj = {};
obj.href = it.url;
obj.img = it.src;
// 设置图片大小
if (index === 1) {
obj.w = '377;';
obj.h = '504;';
it.w = '377;';
it.h = '504;';
} else {
obj.w = '185';
obj.h = '510';
it.w = '185';
it.h = '510';
}
data.category.list.push(obj);
data.category.list.push(it);
});
return data;
};
const getAccordionFloorData = args => {
const _getAccordionFloorData = args => {
let data = {
accordion: {
name: args[0].data.text,
navs: getNavs(args[1].data),
navs: args[1].data,
slide: []
}
};
_.forEach(args[2].data, it => {
let obj = {};
obj.name = it.title;
obj.img = it.src;
obj.href = it.url;
data.accordion.slide.push(obj);
});
data.accordion.slide = args[2].data;
return data;
};
const requestContent = type => {
let data = sign.apiSign({
/* eslint-disable */
client_type: 'web',
/* eslint-enable */
const _requestContent = type => {
let data = {
client_type: 'web',
content_code: channelMap[type || 'boys'].code,
gender: channelMap[type || 'boys'].gender,
page: 1,
limit: 1000
});
};
return serviceApi.get('operations/api/v5/resource/home', data).then(res => {
if (res.code === 200) {
return res;
} else {
log.error('获取资源位接口返回状态码 不是 200');
log.error('response code of "operations/api/v5/resource/home" 200');
return {};
}
});
};
const floorMap = {
slide: getSlideData,
hot: getHotGoodsFloorData,
最新速报: getNewReportFloorData,
人气单品: getSingleHotFloorData,
'GIRL KIDS': getSingleHotFloorData,
'BOY KIDS': getSingleHotFloorData,
优选品牌: getPreBrandTopData,
最新上架: getNewGoodsFloorData,
ad: getadbannerData,
category: getCategoryFloorData,
accordion: getAccordionFloorData,
debrisSlide: getDebrisSlide
slide: _getSlideData,
hot: _getHotGoodsFloorData,
最新速报: _getNewReportFloorData,
人气单品: _getSingleHotFloorData,
'GIRL KIDS': _getSingleHotFloorData,
'BOY KIDS': _getSingleHotFloorData,
优选品牌: _getPreBrandTopData,
最新上架: _getNewGoodsFloorData,
ad: _getadbannerData,
category: _getCategoryFloorData,
accordion: _getAccordionFloorData,
debrisSlide: _getDebrisSlide
};
const processFloorData = (rawData, type) => {
const _processFloorData = (rawData, type) => {
let floorList = [];
let searchPromise = [];
let singlehotFloorIndex = [];
let singlehotFloorTitile = [];
let singlehotFloorTitle = [];
// 定义各种楼层需要用到的接口返回的数组中元素的个数
const bigFloorLength = 5;
const normalFloorLength = 3;
const hotCategoryLength = 2;
... ... @@ -841,7 +537,7 @@ const processFloorData = (rawData, type) => {
} else if (data.template_intro === '热门品类') { // 处理热门品类
floorData = floorMap.hot.call(null, rawData.slice(index, index + hotCategoryLength), type);
} else if (data.data.text) { // 处理一般楼层
let text = getText(data.data.text);
let text = _getText(data.data.text);
let lastIndex = index + bigFloorLength < rawData.length ?
index + bigFloorLength : index + (rawData.length - index - 1);
... ... @@ -876,7 +572,7 @@ const processFloorData = (rawData, type) => {
singlehotFloorIndex.push(floorList.length);
// 记住楼层标题, 以便后面promise获取数据后插入楼层数据
singlehotFloorTitile.push(getText(data.data.text));
singlehotFloorTitle.push(_getText(data.data.text));
} else if (!_.isNil(floorData)) {
_.isArray(floorData) ?
floorList = floorList.concat(floorData) :
... ... @@ -887,26 +583,76 @@ const processFloorData = (rawData, type) => {
return {
floors: floorList,
promise: _.reverse(searchPromise),
singlehotFloorTitile: _.reverse(singlehotFloorTitile),
singlehotFloorTitle: _.reverse(singlehotFloorTitle),
singlehotFloorIndex: _.reverse(singlehotFloorIndex)
};
};
/**
* 获取最新上架商品数据
*
* @param string $channel
* @return array
*/
const getNewArrival = channel => {
let rel = [],
sortList = sortMap[channel],
params = {
order: 'shelve_time:desc',
status: 1,
sales: 'Y',
attribute_not: 2,
stocknumber: 3,
shelve_time: _getShelveTime(20)
};
params.gender = channelMap[channel].gender;
_.forEach(sortList, (item) => {
let data = Object.assign(item, params);
rel.push(_httpBuildQuery(data));
});
return Promise.all(rel).then(res => {
let data = [],
result = [];
_.forEach(sortList, (it, index) => {
if (res[index].data.product_list && res[index].data.product_list.length === sortList[index].viewNum) {
data = data.concat(res[index].data.product_list);
}
});
_.forEach(data, (item) => {
result.push(processProduct(item, {
width: 280,
height: 373
}));
});
return result;
});
};
/**
* 获取频道页数据
* @param {string} type 传入频道页类型,值可以是: boys, girls, kids, lifestyle
* @return {object}
* @param {String} type 传入频道页类型,值可以是: boys, girls, kids, lifestyle
* @return {Object}
*/
exports.getContent = type => {
return Promise.all([headerModel.requestHeaderData(type), requestContent(type)]).then(res => {
const getContent = type => {
return Promise.all([headerModel.requestHeaderData(type), _requestContent(type)]).then(res => {
let headerData = res[0].data || res[0],
contentData = res[1].data ? res[1].data.list : res[1];
let data = {};
const processResult = processFloorData(contentData, type);
const processResult = _processFloorData(contentData, type);
data = headerData;
data.module = 'channel';
... ... @@ -921,7 +667,7 @@ exports.getContent = type => {
floorData: data,
searchPromise: processResult.promise,
singlehotFloorIndex: processResult.singlehotFloorIndex,
singlehotFloorTitile: processResult.singlehotFloorTitile,
singlehotFloorTitle: processResult.singlehotFloorTitle,
channelType: type
};
... ... @@ -933,8 +679,12 @@ exports.getContent = type => {
_.forEach(res, (data, index) => {
result.floorData.channel
.splice(result.singlehotFloorIndex[index], 0,
processFloorDataWithQueryReusult(result.rawData,
result.floorData, data, result.singlehotFloorTitile[index], result.channelType));
_processFloorDataWithQueryReusult(
result.rawData,
data,
result.singlehotFloorTitle[index],
result.channelType
));
});
return result.floorData;
... ... @@ -947,8 +697,8 @@ exports.getContent = type => {
// 优选品牌楼层floorData-ajax
exports.getbrandFloorDataAjax = type => {
return requestContent(type).then(res => {
const getbrandFloorDataAjax = type => {
return _requestContent(type).then(res => {
let contentData = res.data ? res.data.list : [];
let data = {
... ... @@ -974,3 +724,9 @@ exports.getbrandFloorDataAjax = type => {
return data;
});
};
module.exports = {
getNewArrival: getNewArrival,
getContent: getContent,
getbrandFloorDataAjax: getbrandFloorDataAjax
};
... ...
<div class="floor-ad">
<a href="{{href}}" target= "_blank"><img class="lazy" data-original="{{image img 1150 129}}"/></a>
<a href="{{url}}" target= "_blank"><img class="lazy" data-original="{{image src 1150 129}}"/></a>
</div>
... ...
<div class="debris-slider clearfix">
<div class="left-col col">
{{# left}}
<a href="{{href}}" target="_blank">
<a href="{{url}}" target="_blank">
<img class="lazy" data-original="{{image img 200 265}}">
</a>
{{/ left}}
... ... @@ -10,7 +10,7 @@
<ul class="slide-wrapper">
{{# center}}
<li>
<a href="{{href}}" target="_blank">
<a href="{{url}}" target="_blank">
<img class="lazy" data-original="{{image img 570 633}}">
</a>
</li>
... ... @@ -18,16 +18,16 @@
</ul>
<div class="slide-switch">
<a class="prev" href="javascript:;">
<span class="iconfont">&#xe60c;</span>
<span class="iconfont">&#xe609;</span>
</a>
<a class="next" href="javascript:;">
<span class="iconfont">&#xe60b;</span>
<span class="iconfont">&#xe608;</span>
</a>
</div>
</div>
<div class="right-col col">
{{# right}}
<a href="{{href}}" target="_blank">
<a href="{{url}}" target="_blank">
<img class="lazy" data-original="{{image img 200 265}}">
</a>
{{/ right}}
... ...
... ... @@ -3,18 +3,18 @@
<ul class="img-list imgopacity clearfix">
{{# imgBrand}}
<li class="img-item">
<a href="{{href}}" target= "_blank">
<img src="{{image img 378 175}}" alt="">
<a href="{{url}}" target= "_blank">
<img src="{{image src 378 175}}" alt="">
</a>
</li>
{{/ imgBrand}}
</ul>
<div class="img-brand-switch">
<a class="prev" href="javascript:;">
<span class="iconfont">&#xe60c;</span>
<span class="iconfont">&#xe609;</span>
</a>
<a class="next" href="javascript:;">
<span class="iconfont">&#xe60b;</span>
<span class="iconfont">&#xe608;</span>
</a>
</div>
</div>
... ...
... ... @@ -5,26 +5,26 @@
<div class="tpl-nav">
<div class="tpl-keywords">
{{#each keyword}}
<a class="keywords{{@index}}" title="{{name}}" href="{{href}}" target= "_blank"><img class="lazy" src="{{image img 185 152}}"/></a>
<a class="keywords{{@index}}" title="{{title}}" href="{{url}}" target= "_blank"><img class="lazy" src="{{image img 185 152}}"/></a>
{{/each}}
</div>
<div class="tpl-category clearfix">
{{#each category}}
<a href="{{href}}" target= "_blank">{{name}}</a>
<a href="{{url}}" target= "_blank">{{title}}</a>
{{/each}}
</div>
</div>
<div class="tpl-brands imgopacity clearfix">
<ul>
{{#each brands}}
<li><a title="{{name}}" href="{{href}}" target= "_blank"><img class="lazy" src="{{image img 378 248}}"/></a></li>
<li><a title="{{title}}" href="{{url}}" target= "_blank"><img class="lazy" src="{{image img 378 248}}"/></a></li>
{{/each}}
</ul>
</div>
<div class="tpl-types imgopacity clearfix">
<ul>
{{#each types}}
<li><a title="{{name}}" href="{{href}}" target= "_blank"><img class="lazy" src="{{image img 185 248}}"/></a></li>
<li><a title="{{title}}" href="{{url}}" target= "_blank"><img class="lazy" src="{{image img 185 248}}"/></a></li>
{{/each}}
</ul>
</div>
... ... @@ -32,7 +32,7 @@
<div class="tpl-products imgopacity clearfix">
<ul>
{{#each products}}
<li><a href="{{href}}" title="{{name}}" target= "_blank"><img class="lazy" src="{{image img 222 298}}"/></a></li>
<li><a href="{{url}}" title="{{title}}" target= "_blank"><img class="lazy" src="{{image src 222 298}}"/></a></li>
{{/each}}
</ul>
</div>
... ...
... ... @@ -3,9 +3,9 @@
<ul class="g-list imgopacity">
{{#each imgHot}}
{{#if @last}}
<li><a class="impo{{@index}}" href="{{href}}" target= "_blank"><img class="lazy" src="{{image img 378 248}}"/></a></li>
<li><a class="impo{{@index}}" href="{{url}}" target= "_blank"><img class="lazy" src="{{image src 378 248}}"/></a></li>
{{^}}
<li><a class="impo{{@index}}" href="{{href}}" target= "_blank"><img class="lazy" src="{{image img 185 248}}"/></a></li>
<li><a class="impo{{@index}}" href="{{url}}" target= "_blank"><img class="lazy" src="{{image src 185 248}}"/></a></li>
{{/if}}
{{/each}}
</ul>
... ...
... ... @@ -3,8 +3,8 @@
<ul class="clearfix">
{{# list}}
<li class="cate-item{{@index}}">
<a href="{{href}}" target= "_blank">
<img class="lazy" data-original="{{image img w h}}" alt="">
<a href="{{url}}" target= "_blank">
<img class="lazy" data-original="{{image src w h}}" alt="">
</a>
</li>
{{/ list}}
... ...
... ... @@ -4,6 +4,6 @@
<div class="goods-container clearfix">
</div>
<div class="loading">
<a href="{{href}}" target= "_blank">Loading...</a>
<a href="{{url}}" target= "_blank">Loading...</a>
</div>
</div>
... ...
<div class="preference-brand imgopacity">
<div class="img-slider-wrapper clearfix" style="background-color:{{sliderColor}};">
<div class="img-brand-switch">
<a class="prev iconfont" href="javascript:;">&#xe60f;</a>
<a class="next iconfont" href="javascript:;">&#xe60e;</a>
<a class="prev iconfont" href="javascript:;">&#xe609;</a>
<a class="next iconfont" href="javascript:;">&#xe608;</a>
</div>
<div class="img-container-landscape">
<ul class="img-list">
{{# imgBrand}}
<li class="img-item">
<a href="{{href}}" target= "_blank"> <img src="{{image img 320 430}}" alt="{{alt}}"></a>
<a href="{{url}}" target= "_blank"> <img src="{{image src 320 430}}" alt="{{alt}}"></a>
</li>
{{/ imgBrand}}
</ul>
... ...
... ... @@ -5,24 +5,24 @@
<div class="tpl-nav">
<div class="tpl-keywords">
{{#each keyword}}
<a class="keywords{{@index}}" title="{{name}}" href="{{href}}" target= "_blank"><img class="lazy" data-original="{{image img 185 76}}"/></a>
<a class="keywords{{@index}}" title="{{title}}" href="{{url}}" target= "_blank"><img class="lazy" data-original="{{image img 185 76}}"/></a>
{{/each}}
</div>
<div class="tpl-category clearfix">
{{#each category}}
<a href="{{href}}" target= "_blank">{{name}}</a>
<a href="{{url}}" target= "_blank">{{title}}</a>
{{/each}}
</div>
</div>
<div class="tpl-brands imgopacity clearfix">
{{#each brands}}
<a title="{{name}}" href="{{href}}" target= "_blank"><img class="lazy" data-original="{{image img 377 504}}"/></a>
<a title="{{title}}" href="{{url}}" target= "_blank"><img class="lazy" data-original="{{image img 377 504}}"/></a>
{{/each}}
</div>
<div class="tpl-types imgopacity clearfix">
<ul>
{{#each types}}
<li><a title="{{name}}" href="{{href}}" target= "_blank"><img class="lazy" data-original="{{image img 185 504}}"/></a></li>
<li><a title="{{title}}" href="{{url}}" target= "_blank"><img class="lazy" data-original="{{image img 185 504}}"/></a></li>
{{/each}}
</ul>
</div>
... ...
... ... @@ -4,13 +4,13 @@
<ul class="g-list imgopacity clearfix">
{{#each imgHot}}
<li>
<a href="{{href}}" target= "_blank"><div class="commodity-img">
<a href="{{url}}" target= "_blank"><div class="commodity-img">
{{# tip}}
<i class="top">{{.}}</i>
{{/ tip}}
<img class="lazy" data-original="{{image img 280 373}}"/></div>
<img class="lazy" data-original="{{image thumb 280 373}}"/></div>
<p class="commodity-name">{{name}}</p>
<p class="commodity-price"><span>¥{{price}}</span></p>
<p class="commodity-price"><span>¥{{salePrice}}</span></p>
</a>
</li>
{{/each}}
... ... @@ -18,7 +18,7 @@
</div>
<div class="commodity-brands imgopacity clearfix">
{{#each brands}}
<a href="{{href}}" title="{{name}}" target= "_blank"><img class="lazy" data-original="{{image img 185 86}}"/></a>
<a href="{{url}}" title="{{title}}" target= "_blank"><img class="lazy" data-original="{{image src 185 86}}"/></a>
{{/each}}
</div>
</div>
... ...
... ... @@ -6,11 +6,11 @@
{{#unless @last}}
<li>
<a href="{{href}}" target= "_blank">
<a href="{{url}}" target= "_blank">
{{#if @first}}
<img class="lazy" data-original="{{image img 377 504}}" alt="" >
<img class="lazy" data-original="{{image src 377 504}}" alt="" >
{{^}}
<img class="lazy" data-original="{{image img 185 248}}" alt="" >
<img class="lazy" data-original="{{image src 185 248}}" alt="" >
{{/if}}
</a>
</li>
... ... @@ -21,8 +21,8 @@
{{# list}}
{{#if @last}}
<div class="last-item">
<a href="{{href}}" target= "_blank">
<img class="lazy" data-original="{{image img 377 504}}" alt="">
<a href="{{url}}" target= "_blank">
<img class="lazy" data-original="{{image src 377 504}}" alt="">
</a>
</div>
{{/if}}
... ...
... ... @@ -2,7 +2,7 @@
<div class="slide-accordion clearfix">
<ul>
{{#each slide}}
<li><a title="{{name}}" href="{{href}}" target= "_blank"><div class="g-mask"></div><img class="lazy" data-original="{{image img 650 400}}"/></a></li>
<li><a title="{{title}}" href="{{url}}" target= "_blank"><div class="g-mask"></div><img class="lazy" data-original="{{image src 650 400}}"/></a></li>
{{/each}}
</ul>
</div>
... ...
/**
* passport 验证策略注册
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/5/31
*/
'use strict';
const passport = require('passport');
const WeixinStrategy = require('passport-weixin');
const config = require('../../config/common');
let siteUrl = config.siteUrl.indexOf('//') === 0 ? 'http:' + config.siteUrl : config.siteUrl;
/**
* wechat登录
*/
passport.use('wechat', new WeixinStrategy({
clientID: config.thirdLogin.wechat.appID,
clientSecret: config.thirdLogin.wechat.appSecret,
callbackURL: `${siteUrl}/passport/login/wechat/callback`,
requireState: true,
scope: 'snsapi_login'
}, function(accessToken, refreshToken, profile, done) {
done(null, profile);
}));
... ...
/**
* 找回密码
* Created by Tao.Huang on 2016/6/12.
*/
'use strict';
const helpers = global.yoho.helpers;
const service = require('../models/back-service');
const passportHelper = require('../models/passport-helper');
const _ = require('lodash');
helpers.urlFormat = (url, qs) => {
let localhost = 'http://localhost:6002';
if (_.isEmpty(qs)) {
return localhost + url;
}
const queryString = require('queryString');
let str = queryString.stringify(qs);
return localhost + url + '?' + str;
};
/**
* 找回密码主页面
*/
const index = (req, res, next) => {
service.indexPageDataAsync()
.then(result => {
res.render('back/index', Object.assign({
module: 'passport',
page: 'back-index',
title: '找回密码'
}, result));
})
.catch(next);
};
/**
* 校验用户输入信息,是否是已经注册的用户
*/
const validateInputAPI = (req, res, next) => {
let userInput = req.body.phoneNum || '';
let areaCode = (req.body.area || '86').replace('+', '');
service.validateEmailOrMobileAsync(userInput, areaCode)
.then(result => {
req.inputInfo = result;
next();
})
.catch(err => {
res.json({
code: 400,
message: err
});
});
};
/**
* 校验用户输入信息,是否是已经注册的用户
*/
const validateUserPage = (req, res, next) => {
let userInput = req.body.phoneNum || '';
let areaCode = (req.body.area || '86').replace('+', '');
service.validateEmailOrMobileAsync(userInput, areaCode)
.then(result => {
req.inputInfo = result;
next();
})
.catch(()=> {
res.redirect(helpers.urlFormat('/passport/back/index'));
});
};
const getUserInfoAPI = (req, res, next) => {
let inputInfo = req.inputInfo;
service.findUserAsync(inputInfo.type, inputInfo.phone, inputInfo.area)
.then(result => {
res.json(result);
})
.catch(next);
};
const sendCodePage = (req, res, next) => {
let inputInfo = req.inputInfo;
service.sendCodeToUserAsync(inputInfo.type, inputInfo.phone, inputInfo.area)
.then(result => {
if (!(result.code && result.code === 200)) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
return next();
})
.catch(next);
};
const saveInSession = (req, res) => {
switch (req.inputInfo.type) {
case 'email':
{
req.session.email = req.inputInfo.phone;
res.redirect(helpers.urlFormat('/passport/back/sendEmail'));
break;
}
case 'mobile':
{
req.session.mobile = req.inputInfo.phone;
req.session.area = req.inputInfo.area;
res.redirect(helpers.urlFormat('/passport/back/verification'));
break;
}
default:
{
res.redirect(helpers.urlFormat('/passport/back/index'));
}
}
};
const sendBackMobileAPI = (req, res, next) => {
let mobile = req.body.mobile || '';
let area = req.body.area || '86';
service.sendCodeToMobileAsync(area, mobile)
.then(result => {
res.json(result);
})
.catch(next);
};
const validateMobileAPI = (req, res, next) => {
let mobile = req.body.mobile || '';
let area = req.body.area || '86';
const ERR = {code: 400, message: '验证失败'};
if (!passportHelper.validator.isAreaMobile(passportHelper.makeAreaMobile(area, mobile))) {
return res.json(ERR);
}
next();
};
const validateEmailInSession = (req, res, next) => {
let email = req.session.email || '';
if (!email) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
let isp = email.split('@')[1];
const mapperEmailISP = {
'yoho.cn': 'http://smail.yoho.cn'
};
req.body.emailUrl = mapperEmailISP[isp] || `http://mail.${isp}`;
next();
};
const sendEmailPage = (req, res, next) => {
passportHelper.getLeftBannerAsync()
.then(result => {
res.render('back/send-email', Object.assign({
module: 'passport',
page: 'back-send-email-ok',
title: '邮件发送成功'
}, {
sendEmail: {
coverHref: result.url,
coverImg: result.img,
email: req.body.emailUrl
}
}));
})
.catch(next);
};
const validateCodeByEmailPage = (req, res, next) => {
let code = req.query.code || '';
if (!_.isEmpty(req.mobileAuth)) {
return next();
}
service.checkEmailCodeAsync(code)
.then(result => {
if (!result) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
return next();
})
.catch(next);
};
const resetPasswordPage = (req, res, next) => {
let code = req.query.code || '';
passportHelper.getLeftBannerAsync()
.then(result => {
res.render('back/reset-pwd', Object.assign({
module: 'passport',
page: 'back-reset-pwd',
title: '重置密码'
}, {
resetPwd: Object.assign({
coverHref: result.url,
coverImg: result.img,
code: code
}, req.mobileAuth)
}));
})
.catch(next);
};
/**
* 手机验证页面
*/
const verifyCodeByMobilePage = (req, res, next) => {
req.body.mobile = '15062219934';
req.body.area = '86';
req.body.verifyCode = '8933';
passportHelper.getLeftBannerAsync()
.then(result => {
res.render('back/verification', Object.assign({
module: 'passport',
page: 'back-verify-mobile-code',
title: '手机验证'
}, {
verification: {
coverHref: result.url,
coverImg: result.img,
mobile: req.body.mobile,
area: req.body.area,
verifyCode: req.body.verifyCode
}
}));
})
.catch(next);
};
const checkSuccessStatusPage = (req, res, next) => {
let successType = req.session.successType || '';
if (!successType) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
delete req.session.successType;
next();
};
const resetPwdSuccessPage = (req, res, next) => {
passportHelper.getLeftBannerAsync()
.then(result => {
res.render('back/reset-success', Object.assign({
module: 'passport',
page: 'back-index',
title: '重置密码成功'
}, {
resetSuccess: {
coverHref: result.url,
coverImg: result.img
}
}));
})
.catch(next);
};
const verifyCodeByMobileAPI = (req, res) => {
let mobile = req.param('mobile', '');
let area = req.param('area', '86');
let mobileCode = req.param('code', '');
// const session = req.session;
const ERR = {
code: 400,
message: '验证码错误!',
data: helpers.urlFormat('/passport/back/index')
};
// if (!code || mobile !== session.mobile || area !== session.area) {
// return res.json(ERR);
// }
service.verifyCodyByMobileAsync(area, mobile, mobileCode)
.then(result => {
res.json(result);
})
.catch(()=> {
res.json(ERR);
});
};
const validateExistCodePage = (req, res, next) => {
let code = req.query.code || req.body.code;
if (!code) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
next();
};
const validateCodeByMobilePage = (req, res, next) => {
let code = req.query.code || req.body.code;
let mobile = req.query.mobile || req.body.mobile;
let area = req.query.area || req.body.area;
let token = req.query.token || req.body.token;
let createdAt = req.query.createdAt || req.body.createdAt;
if (!mobile) {
req.mobileAuth = {};
return next();
}
let data = {
mobile: mobile,
area: area,
token: token,
createdAt: createdAt
};
code = new Buffer(code, 'base64').toString();
req.mobileAuth = service.authRequest(data, code);
next();
};
const validatePwdPage = (req, res, next) => {
let pwd = req.body.pwd || '';
if (!passportHelper.validator.isPassword(pwd)) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
next();
};
const updatePwdAPI = (req, res, next) => {
let code = req.body.code || '';
let mobileAuth = req.mobileAuth || {};
let newPassword = req.body.pwd || '';
service.updatePwdAsync(code, mobileAuth, newPassword)
.then(result => {
if (result.status) {
req.session.successType = result.type;
res.redirect(helpers.urlFormat('/passport/back/resetSuccess'));
} else {
res.redirect(helpers.urlFormat('/passport/back/index'));
}
})
.catch(next);
};
const validateMobileInSession = (req, res, next) => {
req.body.mobile = req.session.mobile || '';
req.body.verifyCode = req.session.verifyCode || '';
req.body.area = req.session.area || '';
if (!(req.body.mobile && req.body.verifyCode)) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
next();
};
module.exports = {
index,
validateInputAPI,
validateUserPage,
getUserInfoAPI,
sendCodePage,
saveInSession,
sendBackMobileAPI,
validateMobileAPI,
validateEmailInSession,
sendEmailPage,
validateCodeByEmailPage,
resetPasswordPage,
verifyCodeByMobilePage,
checkSuccessStatusPage,
resetPwdSuccessPage,
verifyCodeByMobileAPI,
validateExistCodePage,
validateCodeByMobilePage,
validatePwdPage,
updatePwdAPI,
validateMobileInSession
};
... ...
/**
* Created by TaoHuang on 2016/6/18.
*/
'use strict';
const helpers = global.yoho.helpers;
const requiredAPI = (req, res, next) => {
let captchaToken = (req.body.verifyCode || '').toLowerCase();
if (captchaToken === req.session.captcha) {
return next();
} else {
return res.json({
code: 400,
message: '您输入的验证码不正确!'
});
}
};
const requiredPage = (req, res, next) => {
let captchaToken = (req.body.verifyCode || '').toLowerCase();
if (captchaToken === req.session.captcha) {
return next();
} else {
return res.redirect(helpers.urlFormat('/passport/back/index.html'));
}
};
module.exports = {
requiredAPI,
requiredPage
};
... ...
/**
* 登录
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const passport = require('passport');
const uuid = require('uuid');
const cookie = global.yoho.cookie;
const helpers = global.yoho.helpers;
const log = global.yoho.logger;
const config = global.yoho.config;
const AuthHelper = require('../models/auth-helper');
const loginPage = `${config.siteUrl}/passport/login/index`;
// 第三方登录回调
function doPassportCallback(req, res, next, user) {
let shoppingKey = cookie.getShoppingKey(req);
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = config.siteUrl;
}
if (/sign|login/.test(refer)) {
refer = config.siteUrl;
}
if (user.openId && user.nickname) {
let signinByOpenID;
if (user.sourceType === 'wechat') {
// PC 的微信登录之前使用了 open_id, 所以需要特别的接口处理
signinByOpenID = AuthHelper.signinByWechat(
user.nickname, user.openId, user.unionId, user.sourceType, shoppingKey);
} else {
signinByOpenID = AuthHelper.signinByOpenID(
user.nickname, user.openId, user.sourceType, shoppingKey);
}
signinByOpenID.then((result) => {
if (result.code !== 200) {
return Promise.reject(result);
}
if (result.data['is_bind'] && result.data['is_bind'] === 'N') { //eslint-disable-line
return helpers.urlFormat('/passport/thirdlogin/index', {
openId: user.unionId || user.openId,
sourceType: user.sourceType,
refer: refer
});
} else if (result.code === 200 && result.data.uid) {
return AuthHelper.syncUserSession(result.data.uid, req, res).then(() => {
return refer;
}).catch(next);
}
}).then((redirectTo) => {
res.redirect(redirectTo);
}).catch(() => {
res.redirect(loginPage);
});
} else {
res.redirect(loginPage);
}
}
const wechat = {
beforeLogin: (req, res, next) => {
let refer = req.query.refer;
if (!refer) {
refer = req.get('Referer');
}
refer && res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
next();
},
login: (req, res, next) => {
return passport.authenticate('wechat', {
state: uuid.v4()
})(req, res, next);
},
callback: (req, res, next) => {
passport.authenticate('wechat', (err, user) => {
if (err) {
log.error(`wechat authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
doPassportCallback(req, res, next, {
openId: user._json.openid,
unionId: user._json.unionid || user.id,
nickname: user._json.nickname || user.displayName,
sourceType: 'wechat',
rawUser: user
});
})(req, res, next);
}
};
exports.wechat = wechat;
... ...
/**
* sub app channel
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
var passport = require('passport');
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
}));
require('./auth');
app.use(passport.initialize());
app.use(passport.session());
// router
app.use(require('./router'));
module.exports = app;
... ...
'use strict';
const sign = global.yoho.sign;
const api = global.yoho.API;
class Auth {
static signinByOpenID(nickname, openId, sourceType, shoppingKey) {
let param = {
nickname: nickname,
openId: openId,
source_type: sourceType,
method: 'app.passport.signinByOpenID'
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.get('', param);
}
static signinByWechat(nickname, openId, unionId, sourceType, shoppingKey) {
let param = {
nickname: nickname,
openId: openId,
unionId: unionId,
source_type: sourceType,
method: 'app.passport.signinByWechat'
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.get('', param);
}
static profile(uid) {
let param = {
uid: uid,
method: 'app.passport.profile'
};
return api.get('', param);
}
static syncUserSession(uid, req, res) {
return Auth.profile(uid).then((userInfo) => {
let token = sign.makeToken(uid);
let data = userInfo.data;
if (data) {
let uidCookie = `${data.profile_name}::${data.uid}::${data.vip_info.title}::${token}`;
req.session._TOKEN = token;
req.session._LOGIN_UID = uid;
res.cookie('_UID', uidCookie, {
domain: 'yohobuy.com'
});
}
req.session._TOKEN = token; // esline-disable-line
req.session._LOGIN_UID = uid; // esline-disable-line
res.cookie('_TOKEN', token, {
domain: 'yohobuy.com'
}); // esline-disable-line
}).catch(console.log);
}
}
module.exports = Auth;
... ...
/**
* Created by TaoHuang on 2016/6/15.
*/
'use strict';
const api = global.yoho.API;
const YOHOBUY_URL = 'http://www.yohobuy.com/';
/**
* 获取地区数据
*/
const getAreaDataAsync = () => {
return api.get('', {
method: 'app.passport.getArea'
}).then(result => {
result.data = result.data.map(value => {
value.areaCode = `+${value.area}`;
if (value.areaCode === '+86') {
value.selected = true;
} else {
value.selected = false;
}
delete value.area;
return value;
});
return result;
});
};
/**
* 通过邮箱找回密码
*
* @param string mail 邮箱地址
*/
const sendCodeToEmailAsync = (email) => {
return api.get('', {
method: 'app.register.backpwdByEmail',
email: email
});
};
/**
* 根据邮箱验证码修改密码(调用www.yohobuy.com接口)
*
* @param string pwd 新密码
* @param string code 邮箱验证码
*/
const modifyPasswordByEmailAsync = (pwd, code) => {
const options = {
url: `${YOHOBUY_URL}passport/back/update`,
form: {
pwd: pwd,
're-input': pwd,
code: code
},
timeout: 3000
};
return api._requestFromAPI(options);
};
/**
* 通过手机找回密码
*
* @param string mobile 手机号
* @param integer area 地区码ID
*/
const sendCodeToMobileAsync = (mobile, area) => {
area = area || 86;
return api.get('', {
mobile: mobile,
area: area,
method: 'app.register.sendBackpwdCodeToMobile'
});
};
/**
* 校验密码修改手机验证码
*
* @param string mobile 手机号
* @param string code 验证码
* @param integer area 地区码ID
*/
const validateMobileCodeAsync = (mobile, code, area) => {
area = area || 86;
return api.get('', {
mobile: mobile,
code: code,
area: area,
method: 'app.register.validBackpwdCode'
});
};
/**
* 根据手机验证码修改密码
*
* @param string mobile 手机号
* @param string token 验证手机验证码返回的token
* @param integer area 地区码ID
*/
const modifyPasswordByMobileAsync = (mobile, token, newpwd, area)=> {
area = area || 86;
return api.get('', {
mobile: mobile,
token: token,
newpwd: newpwd,
area: area,
method: 'app.register.changepwdByMobileCode'
});
};
/**
* 验证找回邮件code
*/
const checkEmailCodeAsync = (code) => {
return api.get('', {
code: code,
method: 'web.passport.checkCodeValid'
});
};
/**
* 根据邮箱code修改密码
*/
const modifyPasswordByEmailCodeAsync = (code, password) => {
return api.get('', {
code: code,
newPwd: password,
method: 'app.register.resetPwdByCode'
});
};
module.exports = {
getAreaDataAsync,
sendCodeToEmailAsync,
modifyPasswordByEmailAsync,
sendCodeToMobileAsync,
validateMobileCodeAsync,
modifyPasswordByMobileAsync,
checkEmailCodeAsync,
modifyPasswordByEmailCodeAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/27.
*/
'use strict';
/**
* 签名算法参考微信支付加密算法
* 参考链接:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
*
*/
const _ = require('lodash');
const SALT = '_+@#$%^';
/**
* 生成加密token
* @param data json
* @returns string
*/
const _packageObject = (data) => {
return _.keys(data).sort().map(key => `${key}=${data[key]}`).join('&').toUpperCase();
};
const _encodeMD5 = (str) => {
const md5 = require('md5');
return md5(str).toUpperCase();
};
const makeToken = (data) => {
let saltData = Object.assign(data, {key: SALT});
let str = _packageObject(saltData);
return _encodeMD5(str);
};
const validateToken = (data, token) => {
let saltData = Object.assign(data, {key: SALT});
let str = _packageObject(saltData);
return _encodeMD5(str) === token;
};
module.exports = {
makeToken,
validateToken
};
... ...
/**
* Created by TaoHuang on 2016/6/14.
*/
'use strict';
const helpers = global.yoho.helpers;
const api = require('./back-api');
const Promise = require('bluebird');
const co = Promise.coroutine;
const _ = require('lodash');
const moment = require('moment');
const userService = require('./user-service');
const passportHelper = require('./passport-helper');
const backHelper = require('./back-helper');
const BACK_LEFT_BANNER_CODE = '3bbaf502c447a2ddad60879042e286d8'; // 找回密码左边的banner
const validateEmailOrMobileAsync = (userInput, areaCode) => {
return new Promise(function(resolve, rejected) {
let result = {type: 'email', area: '', phone: ''};
if (passportHelper.validator.verifyEmail(userInput)) {
result.type = 'email';
result.area = '';
result.phone = userInput;
resolve(result);
} else if (passportHelper.validator.isAreaMobile(passportHelper.makeAreaMobile(areaCode, userInput))) {
result.type = 'mobile';
result.area = areaCode;
result.phone = userInput;
resolve(result);
} else {
rejected('输入信息出错!');
}
});
};
const findUserAsync = (type, phone, area) => {
return co(function * () {
const MESSAGE = {
mobile: '您输入的手机号码尚未注册!',
email: '您输入的邮件账户尚未注册!',
ok: '验证成功'
};
const findBy = {
email: userService.findByEmailAsync,
mobile: (phone1, area1) => userService.findByMobileAsync(area1, phone1) // 交换参数
};
const OK = {code: 200, message: MESSAGE.ok};
const user = yield findBy[type](phone, area);
if (_.isEmpty(user)) {
return {
code: 402,
message: MESSAGE[type]
};
}
return OK;
})();
};
const sendCodeToUserAsync = (type, mobile, areaCode) => {
let sendTo = {
email: api.sendCodeToEmailAsync,
mobile: api.sendCodeToMobileAsync
};
return sendTo[type](mobile, areaCode);
};
/**
* 发送找回手机号短信
*/
const sendCodeToMobileAsync = (areaCode, mobile) => {
return api.sendCodeToMobileAsync(mobile, areaCode);
};
/**
* 获得首页的数据
*/
const indexPageDataAsync = () => {
return co(function *() {
let banner = yield passportHelper.getLeftBannerAsync(BACK_LEFT_BANNER_CODE);
let countryList = passportHelper.getCountry();
return {
back: {
coverHref: banner.url,
coverImg: banner.img,
countryCode: 86,
countryName: '中国',
captchaUrl: helpers.urlFormat('/passport/images', {t: moment().unix()}),
countryList: countryList
}
};
})();
};
const verifyCodyByMobileAsync = (area, mobile, mobileCode) => {
const ERR = {
code: 400,
message: '验证码错误!',
data: helpers.urlFormat('/passport/back/index')
};
return api.validateMobileCodeAsync(mobile, mobileCode, area)
.then(result => {
if (!(result.code && result.code === 200)) {
return ERR;
}
let data = {
mobile: mobile,
area: area,
token: result.data.token,
createdAt: moment().unix()
};
data.code = new Buffer(backHelper.makeToken(data)).toString('base64');
return {
code: 200,
message: '验证成功',
data: helpers.urlFormat('/passport/back/backcode', data)
};
});
};
const authRequest = (data, token) => {
if (!backHelper.validateToken(data, token)) {
return {};
}
let existTime = moment.duration(1, 'hours').seconds();
let isExpired = (moment().unix() - data.createdAt) > existTime;
if (isExpired) {
return {};
} else {
return data;
}
};
const updatePwdAsync = (emailToken, mobileToken, newPassword) => {
return co(function * () {
let result = {type: 'mobile', status: false};
const ERR = {type: 'unknown', status: false};
if (!_.isEmpty(mobileToken)) {
if (!mobileToken.mobile || mobileToken.uid) {
return ERR;
}
let mobile = mobileToken.mobile;
let area = mobileToken.area;
let token = mobileToken.token;
let modifyStatus = yield api.modifyPasswordByMobileAsync(mobile, token, newPassword, area);
if (!modifyStatus.code || modifyStatus.code !== 200) {
return ERR;
}
result.type = 'mobile';
result.status = true;
} else {
let modifyStatus = yield api.modifyPasswordByEmailCodeAsync(emailToken, newPassword);
if (!modifyStatus.code || modifyStatus.code !== 200) {
return ERR;
}
result.type = 'email';
result.status = true;
}
return result;
})();
};
const checkEmailCodeAsync = api.checkEmailCodeAsync;
module.exports = {
validateEmailOrMobileAsync,
findUserAsync,
sendCodeToUserAsync,
sendCodeToMobileAsync,
indexPageDataAsync,
verifyCodyByMobileAsync,
authRequest,
updatePwdAsync,
checkEmailCodeAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
'use strict';
const serviceAPI = global.yoho.ServiceAPI;
module.exports.getResourceAsync = (resourceCode) => {
return serviceAPI.get('/operations/api/v5/resource/get', {
content_code: resourceCode
});
};
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
const api = require('./index-api');
module.exports.getResourceAsync = (resourceCode) => {
return api.getResourceAsync(resourceCode)
.then(result => {
if (result.code === 200) {
return result.data;
} else {
return {};
}
})
.catch(() => {
return {};
});
};
... ...
/**
* Created by TaoHuang on 2016/6/20.
*/
'use strict';
const helpers = global.yoho.helpers;
const Promise = require('bluebird');
const co = Promise.coroutine;
const _ = require('lodash');
const indexService = require('./index-service');
const getLeftBannerAsync = (resourceCode) => {
const DEFAULT_VALUE = {
img: 'http://img12.static.yhbimg.com/' +
'yhb-img01/2015/12/01/07/020a0b6e7ff908d0c2bc4045b4fef42b9f.png?imageView/2/w/252/h/190',
url: ''
};
return co(function * () {
let resource = yield indexService.getResourceAsync(resourceCode);
if (_.isEmpty(resource)) {
return DEFAULT_VALUE;
}
let value = {};
// 有点问题 // passport model 58
value.img = helpers.image(resource[0].data[0].src, 252, 190);
value.url = resource[0].data[0].url;
return value;
})();
};
const getCountry = () => {
return [
{
areaCode: '+61',
selected: false,
name: '澳大利亚'
},
{
areaCode: '+82',
selected: false,
name: '韩国'
},
{
areaCode: '+1',
selected: false,
name: '加拿大'
},
{
areaCode: '+60',
selected: false,
name: '马来西亚'
},
{
areaCode: '+1',
selected: false,
name: '美国'
},
{
areaCode: '+81',
selected: false,
name: '日本'
},
{
areaCode: '+65',
selected: false,
name: '新加坡'
},
{
areaCode: '+44',
selected: false,
name: '英国'
},
{
areaCode: '+86',
selected: true,
name: '中国'
},
{
areaCode: '+853',
selected: false,
name: '中国澳门'
},
{
areaCode: '+886',
selected: false,
name: '中国台湾'
},
{
areaCode: '+852',
selected: false,
name: '中国香港'
}
];
};
/**
* 验证邮箱是否合法
*
* @param string email
* @return boolean
*/
const verifyEmail = email => {
if (!email) {
return false;
}
const emailRegExp = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
return emailRegExp.test(email);
};
/**
* 各国手机号规则
*/
function _areaMobileVerify(phone, area) {
area = area || '86';
phone = phone.trim();
let verify = {
86: {
name: '中国',
match: /^1[3|4|5|8|7][0-9]{9}$/.test(phone)
},
852: {
name: '中国香港',
match: /^[9|6|5][0-9]{7}$/.test(phone)
},
853: {
name: '中国澳门',
match: /^[0-9]{8}$/.test(phone)
},
886: {
name: '中国台湾',
match: /^[0-9]{10}$/.test(phone)
},
65: {
name: '新加坡',
match: /^[9|8][0-9]{7}$/.test(phone)
},
60: {
name: '马来西亚',
match: /^1[1|2|3|4|6|7|9][0-9]{8}$/.test(phone)
},
1: {
name: '加拿大&美国',
match: /^[0-9]{10}$/.test(phone)
},
82: {
name: '韩国',
match: /^01[0-9]{9}$/.test(phone)
},
44: {
name: '英国',
match: /^7[7|8|9][0-9]{8}$/.test(phone)
},
81: {
name: '日本',
match: /^0[9|8|7][0-9]{9}$/.test(phone)
},
61: {
name: '澳大利亚',
match: /^[0-9]{11}$/.test(phone)
}
};
if (verify[area]) {
return verify[area].match;
} else {
return false;
}
}
/**
* 验证国际手机号是否合法
*/
const isAreaMobile = areaMobile => {
if (!areaMobile) {
return false;
}
let mobile = {
area: '86',
phone: ''
};
let splitMobile = areaMobile.split('-');
if (splitMobile.length === 2) {
mobile.area = splitMobile[0];
mobile.phone = splitMobile[1];
} else {
mobile.phone = splitMobile[0];
}
return _areaMobileVerify(mobile.phone, mobile.area);
};
/**
* 验证手机是否合法
*/
const verifyMobile = phone => {
if (!phone) {
return false;
}
return /^1[3|4|5|8|7][0-9]{9}$/.test(phone);
};
const verifyPassword = password => {
if (!password) {
return false;
}
return /^([a-zA-Z0-9\-\+_!@\#$%\^&\*\(\)\:\;\.=\[\]\\\',\?]){6,20}$/.test(password);
};
const makeAreaMobile = (area, mobile) => {
if (!area || area === '86') {
return mobile;
}
return `${area}-${mobile}`;
};
const isPassword = pwd => {
if (!pwd) {
return false;
}
let pwdRegexp = /^([a-zA-Z0-9\-\+_!@\#$%\^&\*\(\)\:\;\.=\[\]\\\',\?]){6,20}$/;
return pwdRegexp.test(_.trim(pwd));
};
module.exports = {
validator: {
verifyPassword,
verifyMobile,
isAreaMobile,
verifyEmail,
isPassword
},
makeAreaMobile,
getCountry,
getLeftBannerAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/17.
*/
'use strict';
const _ = require('lodash');
const api = global.yoho.API;
const EMPTY = {};
/**
* 根据手机号获取用户信息
*/
const findByMobileAsync = (area, mobile) => {
return api.get('', {
mobile: mobile,
area: area,
method: 'app.passport.getProfileByMobile'
})
.then(result => {
if (!result.code || result.code !== 200 || !result.data || _.isEmpty(result.data)) {
return EMPTY;
}
return result.data;
})
.catch(() => {
return EMPTY;
});
};
/**
* 根据邮箱获取用户信息
*/
const findByEmailAsync = (email) => {
return api.get('', {
email: email,
method: 'app.passport.getProfileByEmail'
})
.then(result => {
if (!result.code || result.code !== 200 || !result.data || _.isEmpty(result.data)) {
return EMPTY;
}
return result.data;
})
.catch(() => {
return EMPTY;
});
};
module.exports = {
findByMobileAsync,
findByEmailAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/17.
*/
'use strict';
const api = require('./user-api');
module.exports = api;
... ...
/**
* router of sub app channel
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const express = require('express');
const cRoot = './controllers';
const login = require(cRoot + '/login');
const captcha = require(cRoot + '/captcha');
const back = require(cRoot + '/back');
const router = express.Router(); // eslint-disable-line
router.get('/autosign/wechat', login.wechat.beforeLogin, login.wechat.login); // 微信登录, 兼容 PHP 的路径
router.get('/login/wechat/callback', login.wechat.callback);
/**
* 找回密码首页信息
*/
// 找回密码首页
router.get('/back/index', back.index);
// 实时验证输入是否正确
router.post('/back/authcode',
// captcha.requiredAPI,
back.validateInputAPI,
back.getUserInfoAPI);
// 提交按钮邮件API
router.post('/back/email',
// Captcha.requiredPage,
back.validateUserPage,
back.sendCodePage,
back.saveInSession);
// 提交按钮手机API
router.post('/back/mobile',
captcha.requiredPage,
back.validateUserPage,
back.sendCodePage,
back.saveInSession);
/**
* 邮件找回密码
*/
// 发送邮件成功页面
router.get('/back/sendEmail',
back.validateEmailInSession,
back.sendEmailPage);
/**
* 短信找回密码
*/
// 验证手机短信页面
router.get('/back/verification',
// Back.validateMobileInSession,
// Captcha.requiredPage,
back.verifyCodeByMobilePage);
// 重新发送短信接口
router.post('/back/sendBackMobile',
// Captcha.requiredAPI,
back.validateMobileAPI,
back.sendBackMobileAPI);
// 验证手机验证码接口
router.post('/back/backMobile',
// Captcha.requiredAPI,
back.verifyCodeByMobileAPI);
/**
* 重置密码
*/
// 重置密码页面
router.get('/back/backcode',
back.validateExistCodePage,
back.validateCodeByMobilePage,
back.validateCodeByEmailPage,
back.resetPasswordPage);
// 重置密码接口
router.post('/back/update',
back.validateExistCodePage,
back.validateCodeByMobilePage,
// Back.validatePwdPage,
back.updatePwdAPI);
// 重置密码成功页面
router.get('/back/resetSuccess',
// Back.checkSuccessStatusPage,
back.resetPwdSuccessPage);
module.exports = router;
... ...
<div class="back-page passport-page yoho-page clearfix">
{{# back}}
{{> back/cover}}
<div class="content">
<div class="back-header clearfix">
<h2 class="title">找回密码</h2>
<span id="country-code" class="country-code">
<em>{{countryName}} +{{countryCode}}</em>
<i class="iconfont">&#xe61d;</i>
</span>
<ul id="country-code-list" class="country-code-list">
{{# countryList}}
<li data-cc="{{areaCode}}">{{name}} {{areaCode}}</li>
{{/ countryList}}
</ul>
</div>
<form id="back-form" class="back-form" action="/passport/back/email" method="post">
<input id="country-code-hide" type="hidden" name="area" value="+86">
<ul>
<li class="input-container-li clearfix">
<input id="phone-num" class="input va phone-num" type="text" name="phoneNum" placeholder="邮箱/手机号码" autocomplete="off">
<span id="account-err" class="err-tip hide">
<i></i>
<em>账户名不能为空</em>
</span>
</li>
<li class="input-container-li clearfix">
<input id="captcha" class="input va captcha" type="text" name="verifyCode" placeholder="验证码" autocomplete="off" maxlength="4">
<img id="captcha-img" class="captcha-img" src="{{captchaUrl}}" alt="">
<a id="change-captcha" class="link change-captcha">换一张</a>
<span id="captcha-err" class="err-tip captcha-err hide">
<i></i>
<em>验证码不能为空</em>
</span>
</li>
<li class="input-container-li clearfix">
<input name="refer" id="refer" type="hidden" value="http%3A%2F%2Fwww.yohobuy.com%2F">
<input id="find-btn" class="btn find-btn disable" type="submit" value="下一步" disabled="">
</li>
</ul>
</form>
</div>
{{/ back}}
</div>
... ...
<div class="reset-pwd-page back-page passport-page yoho-page clearfix">
{{# resetPwd}}
{{> back/cover}}
<div class="content">
<h2 class="title2">重置密码</h2>
<form id="reset-pwd-form" class="reset-pwd-form" method="POST" action="/passport/back/update">
<ul>
<li class="input-container-li po-re">
<input id="pwd" class="input va pwd" type="password" name="pwd" placeholder="新密码"
maxlength="20">
<div class="pwd-intensity-container">
<span class="pwd-intensity low"></span>
<span class="pwd-intensity mid"></span>
<span class="pwd-intensity high"></span>
</div>
<div id="pwd-tips" class="pwd-tips hide">
<div class="default" id="pwd-tip1">
<i></i>
密码只支持6-20位字符
</div>
<div class="default" id="pwd-tip2">
<i></i>
由字母、 数字组合,不能包含特殊符号
</div>
</div>
<span id="pwd-err" class="err-tip hide">
<i></i>
<em>请输入密码</em>
</span>
</li>
<li class="input-container-li clearfix po-re">
<input id="re-input" class="input va re-input repwd" type="password" name="re-input"
placeholder="再次输入" maxlength="20">
<span id="repwd-err" class="err-tip hide">
<i></i>
<em>请输入密码确认</em>
</span>
</li>
<li class="input-container-li clearfix">
<input type="hidden" name="code" value="{{code}}">
<input type="hidden" name="mobile" value="{{mobile}}">
<input type="hidden" name="area" value="{{area}}">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="create_time" value="{{create_time}}">
<input id="reset-pwd-btn" class="btn reset-pwd-btn" type="submit" value="提交" disabled="">
</li>
</ul>
</form>
</div>
{{/ resetPwd}}
</div>
... ...
<div class="reset-success-page back-page passport-page yoho-page clearfix">
{{# resetSuccess}}
{{> back/cover}}
<div class="content">
<div class="success-text">
<i class="iconfont">&#xe620;</i><span>恭喜!</span>密码修改成功,&nbsp;<span id="count-down">5</span>&nbsp;&nbsp;秒后将跳转至首页
</div>
<a class="success-btn" href="/">随便逛逛</a>
</div>
{{/ resetSuccess}}
</div>
<script type="text/javascript">
(function() {
var count = 5,
countDown = document.getElementById('count-down');
var timer = setInterval(function(){
if (count > 1) {
count--;
countDown.innerHTML = count;
} else {
location.href = '/';
}
}, 1000);
})();
</script>
... ...
<div class="send-email-page passport-page yoho-page clearfix">
{{# sendEmail}}
{{> back/cover}}
<div class="content">
<div class="send-tips"><i class="iconfont">&#xe61e;</i>我们已经把验证邮件发送至您的邮箱,请在24小时内通过邮件内的<br>链接继续设置新的密码。</div>
<div class="no-find">没有收到?到您邮箱的垃圾邮件里找找。</div>
<div class="to-my-email">
<a href="{{email}}" target="_blank" class="btn_b_ar_r">去我的邮箱&gt;</a>
</div>
</div>
{{/ sendEmail}}
</div>
... ...
<div class="verification-page back-page passport-page yoho-page clearfix">
{{# verification}}
{{> back/cover}}
<div class="content">
<form id="verification-form" class="verification-form" method="POST" action="/passport/back/backmobile">
<ul>
<li class="head-title">验证身份</li>
<li class="po-re">
<label class="pn-label">手机号码</label>
<span class="country-code">+{{area}}</span>
<span class="phone-num">{{mobile}}</span>
</li>
<li class="po-re">
<input id="captcha" class="input va captcha" type="text" name="code" maxlength="4">
<input id="send-captcha" class="btn send-captcha" type="button" value="发送验证码" disabled="">
<div id="captcha-tip" class="captcha-tips"><i class="iconfont">&#xe61f;</i>验证码已发送至您的手机,请查收</div>
<span id="err-tip" class="err-tip hide">
<i></i>
<em>请输入验证码</em>
</span>
</li>
<li>
<input name="area" id="area" type="hidden" value="{{area}}">
<input name="mobile" id="mobile" type="hidden" value="{{mobile}}">
<input name="verifyCode" id="captchaPic" type="hidden" value="{{verifyCode}}">
<input name="refer" id="refer" type="hidden" value="">
<a id="next-step" class="btn next-step disable" href="javascript:;">下一步</a>
<!-- <input id="next-step" class="btn next-step disable" type="submit" value="下一步" disabled=""> -->
</li>
</ul>
</form>
</div>
{{/ verification}}
</div>
... ...
<div class="passport-cover">
<div class="cover-content">
{{#if coverHref}}
<a href="{{coverHref}}" target="_bank">
<img class="cover-img" src="{{coverImg}}">
</a>
{{^}}
<img class="cover-img" src="{{coverImg}}">
{{/if}}
</div>
</div>
... ...
... ... @@ -6,10 +6,7 @@
'use strict';
const library = '../../../library';
const helpers = require(`${library}/helpers`);
const helpers = global.yoho.helpers;
const mRoot = '../models';
const service = require(`${mRoot}/detail-service`);
const detailHelper = require('../models/detail-helper');
... ... @@ -17,13 +14,11 @@ const detailHelper = require('../models/detail-helper');
/**
* 单个商品详情
*/
module.exports.showMain = (req, res, next) => {
const showMain = (req, res, next) => {
// TODO: vipLevel = 0; // 用户等级
let pid = 204503;
let uid = req.user.uid || '';
let channel = detailHelper.COOKIE_NAME_BOYS;
if (req.cookies._Channel) {
... ... @@ -41,67 +36,70 @@ module.exports.showMain = (req, res, next) => {
gender: gender,
uid: uid,
vipLevel: 0
}).then((result) => {
res.render('product/detail', Object.assign({
module: 'product',
page: 'detail'
}, result));
}).catch(next);
})
.then(result => {
res.render('product/detail', Object.assign({
module: 'product',
page: 'detail'
}, result));
})
.catch(next);
};
/**
* 获取热区图
*/
module.exports.indexHotArea = (req, res, next) => {
const indexHotArea = (req, res, next) => {
let pid = req.query.productId || 0;
service.indexHotAreaAsync(pid).then(result => {
res.render('product/hotarea', {
hotArea: result,
layout: false
});
}).catch(next);
service.indexHotAreaAsync(pid)
.then(result => {
res.render('product/hotarea', {
hotArea: result,
layout: false
});
})
.catch(next);
};
/**
* 获得评论列表
* json
*/
module.exports.indexComment = (req, res, next) => {
const indexComment = (req, res, next) => {
let pid = req.query.productId || 0;
let page = req.query.page || 1;
let size = req.query.size || 10;
service.indexCommentAsync(pid, page, size).then(result => {
res.json(result);
}).catch(next);
service.indexCommentAsync(pid, page, size)
.then(result => {
res.json(result);
})
.catch(next);
};
/**
* 获得咨询列表
* json
*/
module.exports.indexConsult = (req, res, next) => {
const indexConsult = (req, res, next) => {
let uid = req.user.uid || '';
let pid = req.query.productId || 0;
let page = req.query.page || 1;
let size = req.query.size || 10;
service.indexConsultAsync(uid, pid, page, size).then(result => {
res.json(result);
}).catch(next);
service.indexConsultAsync(uid, pid, page, size)
.then(result => {
res.json(result);
})
.catch(next);
};
/**
* 新建咨询
* json
*/
module.exports.createConsult = (req, res, next) => {
const createConsult = (req, res, next) => {
let uid = req.user.uid || '';
let pid = req.body.productId || 0;
... ... @@ -109,9 +107,11 @@ module.exports.createConsult = (req, res, next) => {
let content = req.body.content;
if (content && uid) {
service.createConsultAsync(uid, pid, content).then(result => {
res.json(result);
}).catch(next);
service.createConsultAsync(uid, pid, content)
.then(result => {
res.json(result);
})
.catch(next);
} else if (!content) {
res.json({
... ... @@ -124,7 +124,7 @@ module.exports.createConsult = (req, res, next) => {
code: 403,
message: '用户没有登录',
data: {
url: helpers.urlFormat('/signin.html')
url: helpers.urlFormat('/signin')
}
});
... ... @@ -138,4 +138,11 @@ module.exports.createConsult = (req, res, next) => {
};
module.exports = {
showMain,
indexHotArea,
indexComment,
indexConsult,
createConsult
};
... ...
... ... @@ -3,30 +3,29 @@
*/
'use strict';
const library = '../../../library';
const helpers = require(`${library}/helpers`);
const helpers = global.yoho.helpers;
const brandService = require('../models/favorite-brand-service');
const productService = require('../models/favorite-product-service');
/**
* 收藏品牌ajax请求
*/
module.exports.changeFavoriteBrand = (req, res, next) => {
const changeFavoriteBrand = (req, res, next) => {
let uid = req.user.uid || '';
let brandId = req.body.brandId;
if (uid && brandId) {
brandService.changeAsync(uid, brandId).then(result => {
res.json(result);
}).catch(next);
brandService.changeAsync(uid, brandId)
.then(result => {
res.json(result);
})
.catch(next);
} else if (!uid) {
res.json({
code: 403,
message: '用户ID不存在',
data: {
url: helpers.urlFormat('signin.html')
url: helpers.urlFormat('/signin')
}
});
} else {
... ... @@ -37,24 +36,24 @@ module.exports.changeFavoriteBrand = (req, res, next) => {
}
};
module.exports.collectProduct = (req, res, next) => {
const collectProduct = (req, res, next) => {
let uid = req.user.uid || '';
let pid = req.body.productId;
let type = req.body.type || 'add';
if (uid && pid) {
switch (type) {
case 'add':
{
productService.createAsync(uid, pid).then(result => {
productService.createAsync(uid, pid)
.then(result => {
if (result.code === 413) {
result.message = '该商品已经收藏';
}
res.json(result);
}).catch(next);
})
.catch(next);
break;
}
case 'cancel':
... ... @@ -77,7 +76,7 @@ module.exports.collectProduct = (req, res, next) => {
code: 403,
message: '用户没有登录',
data: {
url: helpers.urlFormat('signin.html')
url: helpers.urlFormat('/signin')
}
});
} else {
... ... @@ -87,3 +86,8 @@ module.exports.collectProduct = (req, res, next) => {
});
}
};
module.exports = {
changeFavoriteBrand,
collectProduct
};
... ...
... ... @@ -23,7 +23,7 @@ exports.getProductPic = (req, res) => {
* @param {[type]} res [description]
* @return {[type]} [description]
*/
exports.index = (req, res) => {
exports.index = (req, res, next) => {
let params = Object.assign({
order: 's_t_asc,s_s_asc'
}, req.query);
... ... @@ -49,9 +49,6 @@ exports.index = (req, res) => {
responseData.resultShow = JSON.stringify(result, null, 4);
responseData.headerData = headerData;
res.render('sale/other', responseData);
}).catch(() => {
responseData.pageErr = true;
res.render('error', responseData);
});
}).catch(next);
};
... ...
... ... @@ -14,23 +14,18 @@ const outlets = require(`${mRoot}/outlets`);
* @param {[type]} res [description]
* @return {[type]} [description]
*/
exports.index = (req, res) => {
exports.index = (req, res, next) => {
let resData = {
module: 'product',
page: 'outlets'
};
let channel = req.query.channel || req.cookies._Channel || 'boys';
let channel = req.query.channel || req.cookies._Channel || 'otltIdxDflt';
outlets.getOutletsIndexData(req.query, channel).then(result => {
resData.result = result;
resData.headerData = result.headerData;
resData.resultShow = JSON.stringify(result, null, 4);
Object.assign(resData, result);
res.render('outlets/index', resData);
}).catch(() => {
resData.pageErr = true;
res.render('error', resData);
});
}).catch(next);
};
... ... @@ -40,18 +35,16 @@ exports.index = (req, res) => {
* @param {[type]} res [description]
* @return {[type]} [description]
*/
exports.channel = (req, res) => {
exports.channel = (req, res, next) => {
let resData = {
module: 'product',
page: 'outlets'
};
let channel = req.params.channel || req.query.channel || req.cookies._Channel || 'boys';
outlets.getOutletsChannelData(req.query, req.params.channel).then(result => {
outlets.getOutletsChannelData(req.query, channel).then(result => {
res.render('outlets/channel', Object.assign(resData, result));
}).catch(() => {
resData.pageErr = true;
res.render('error', resData);
});
}).catch(next);
};
/**
... ... @@ -60,7 +53,7 @@ exports.channel = (req, res) => {
* @param {[type]} res [description]
* @return {[type]} [description]
*/
exports.special = (req, res) => {
exports.special = (req, res, next) => {
let params = req.query;
let channel = req.query.channel || req.cookies._Channel || 'boys';
let resData = {
... ... @@ -72,15 +65,9 @@ exports.special = (req, res) => {
resData.pageErr = true;
}
// 模拟数据
// resData = outletsSimulation.special();
outlets.getOutletsSpecialData(params, channel).then(result => {
res.render('outlets/special', Object.assign(resData, result));
}).catch(() => {
resData.pageErr = true;
res.render('error', resData);
});
}).catch(next);
};
/**
... ... @@ -89,7 +76,7 @@ exports.special = (req, res) => {
* @param {[type]} res [description]
* @return {[type]} [description]
*/
exports.list = (req, res) => {
exports.list = (req, res, next) => {
let params = req.query;
let channel = req.query.channel || req.cookies._Channel || 'boys';
... ... @@ -102,11 +89,7 @@ exports.list = (req, res) => {
};
outlets.getOutletsCategoryData(params, channel).then((result) => {
responseData.resultShow = JSON.stringify(result, null, 4);
res.render('sale/other', Object.assign(responseData, result));
}).catch(() => {
responseData.pageErr = true;
res.render('error', responseData);
});
}).catch(next);
};
... ...
... ... @@ -20,8 +20,7 @@ exports.index = (req, res) => {
let responseData = {
module: 'product',
page: 'sale',
footerTop: true
page: 'sale'
};
// 真实数据输出
... ... @@ -52,8 +51,7 @@ exports.discount = (req, res) => {
let responseData = {
module: 'product',
page: 'sale',
footerTop: true
page: 'sale'
};
sale.getSaleDiscountData(params, channel).then((result) => {
... ... @@ -86,8 +84,7 @@ exports.vip = (req, res) => {
let responseData = {
module: 'product',
page: 'sale',
footerTop: true
page: 'sale'
};
sale.getSaleOthersData(params, channel).then((result) => {
... ... @@ -110,7 +107,6 @@ exports.vip = (req, res) => {
*/
exports.newSale = (req, res) => {
let params = Object.assign({
saleType: '3',
order: 's_t_desc'
}, req.query);
... ... @@ -118,8 +114,7 @@ exports.newSale = (req, res) => {
let responseData = {
module: 'product',
page: 'sale',
footerTop: true
page: 'sale'
};
sale.getSaleOthersData(params, channel).then((result) => {
... ... @@ -148,8 +143,7 @@ exports.breakingYards = (req, res) => {
let responseData = {
module: 'product',
page: 'sale',
footerTop: true
page: 'sale'
};
sale.getSalebreakingYardsData(params, channel).then((result) => {
... ...
... ... @@ -10,22 +10,21 @@ var express = require('express'),
var app = express();
// set view engin
var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
var partials = path.join(__dirname, './views');
app.on('mount', function (parent) {
app.on('mount', function(parent) {
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
});
// set view engin
app.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [`${partials}/partial`, `${doraemon}/partial`],
helpers: require('../../library/helpers')
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
// router
... ...
'use strict';
/**
* Created by TaoHuang on 2016/6/14.
*/
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
'use strict';
const api = new API();
const api = global.yoho.API;
module.exports.getBannerInfoAsync = (bid) => {
return api.get('', sign.apiSign({
const getBannerInfoAsync = bid => {
return api.get('', {
method: 'web.brand.banner',
brand_id: bid
}));
});
};
module.exports.getBrandLogoByDomainAsync = (domain) => {
return api.get('', sign.apiSign({
const getBrandLogoByDomainAsync = domain => {
return api.get('', {
domain: domain,
method: 'web.brand.byDomain'
}));
});
};
module.exports = {
getBannerInfoAsync,
getBrandLogoByDomainAsync
};
... ...
... ... @@ -5,10 +5,9 @@
const Promise = require('bluebird');
const co = Promise.coroutine;
const api = require('./brand-api');
module.exports.getBrandByDomainAsync = (domain) => {
const getBrandByDomainAsync = domain => {
return co(function*() {
let brandInfo = yield api.getBrandLogoByDomainAsync(domain);
... ... @@ -33,4 +32,7 @@ module.exports.getBrandByDomainAsync = (domain) => {
})();
};
module.exports.getBannerInfoAsync = api.getBannerInfoAsync;
module.exports = {
getBrandByDomainAsync,
getBannerInfoAsync: api.getBannerInfoAsync
};
... ...
... ... @@ -4,25 +4,21 @@
'use strict';
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const log = require(`${library}/logger`);
const api = new API();
const api = global.yoho.API;
/**
* 获取评论
*/
module.exports.indexAsync = function(pid, page, size) {
return api.get('', sign.apiSign({
const indexAsync = (pid, page, size) => {
return api.get('', {
method: 'app.comment.li',
product_id: pid,
page: page,
limit: size
})).catch(log.error);
});
};
module.exports = {
indexAsync
};
... ...
... ... @@ -8,23 +8,19 @@ const Promise = require('bluebird');
const co = Promise.coroutine;
const _ = require('lodash');
const library = '../../../library';
const helpers = require(`${library}/helpers`);
const helpers = global.yoho.helpers;
const api = require('./detail-comment-api');
const detailHelper = require('./detail-helper');
module.exports.indexAsync = (pid, page, size) => {
const indexAsync = (pid, page, size) => {
return co(function *() {
let commentList = yield api.indexAsync(pid, page, size);
if (!commentList.code || !commentList.code !== 200) {
if (!commentList.code && commentList.code === 200) {
return [];
}
return commentList.data.map(value => {
let item = {};
... ... @@ -48,3 +44,7 @@ module.exports.indexAsync = (pid, page, size) => {
})();
};
module.exports = {
indexAsync
};
... ...
... ... @@ -4,18 +4,12 @@
'use strict';
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const log = require(`${library}/logger`);
const api = new API();
const api = global.yoho.API;
/**
* 咨询内容列表
*/
module.exports.indexAsync = function (uid, pid, page, size) {
const indexAsync = function(uid, pid, page, size) {
let param = {};
param.method = 'app.consult.li';
... ... @@ -28,17 +22,22 @@ module.exports.indexAsync = function (uid, pid, page, size) {
param.page = page;
param.limit = size;
return api.get('', sign.apiSign(param)).catch(log.error);
return api.get('', param);
};
/**
* 添加咨询操作
*/
module.exports.createAsync = function (uid, pid, content) {
return api.post('', sign.apiSign({
const createAsync = function(uid, pid, content) {
return api.post('', {
method: 'h5.consult.add',
product_id: pid,
content: content,
uid: uid
})).catch(log.error);
});
};
module.exports = {
indexAsync,
createAsync
};
... ...
... ... @@ -10,8 +10,7 @@ const co = Promise.coroutine;
const api = require('./detail-consult-api');
const detailHelper = require('./detail-helper');
module.exports.indexAsync = (uid, pid, page, size) => {
const indexAsync = (uid, pid, page, size) => {
return co(function *() {
let consultList = yield api.indexAsync(uid, pid, page, size);
... ... @@ -37,4 +36,7 @@ module.exports.indexAsync = (uid, pid, page, size) => {
})();
};
module.exports.createAsync = api.createAsync;
module.exports = {
indexAsync,
createAsync: api.createAsync
};
... ...
... ... @@ -7,22 +7,21 @@
const moment = require('moment');
/* COOKIE标识访问的是男生频道 */
const COOKIE_NAME_BOYS = module.exports.COOKIE_NAME_BOYS = 'boys';
const COOKIE_DOMAIN = module.exports.COOKIE_DOMAIN = '.yohobuy.com';
const COOKIE_NAME_BOYS = 'boys';
const COOKIE_DOMAIN = '.yohobuy.com';
// 商品详情页的默认头像
module.exports.DEFAULT_AVATAR_ICO = 'http://static.yohobuy.com/images/v3/boy.jpg';
const DEFAULT_AVATAR_ICO = 'http://static.yohobuy.com/images/v3/boy.jpg';
const IMAGE_SERVICE_URL = 'http://head.static.yhbimg.com/yhb-head/';
module.exports.IMAGE_SERVICE_URL = 'http://head.static.yhbimg.com/yhb-head/';
module.exports.setSwitchToCookie = (res) => {
const setSwitchToCookie = (res) => {
res.cookie('_Channel', COOKIE_NAME_BOYS, {
domain: COOKIE_DOMAIN,
maxAge: moment.duration(300, 'days').seconds()
});
};
module.exports.getGenderByCookie = (req) => {
const getGenderByCookie = (req) => {
let gender = null;
let channel = req.cookies._Channel || 'boys';
... ... @@ -46,3 +45,13 @@ module.exports.getGenderByCookie = (req) => {
return gender;
};
module.exports = {
COOKIE_NAME_BOYS,
COOKIE_DOMAIN,
DEFAULT_AVATAR_ICO,
IMAGE_SERVICE_URL,
setSwitchToCookie,
getGenderByCookie
};
... ...
... ... @@ -2,19 +2,20 @@
* Created by TaoHuang on 2016/6/13.
*/
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const log = require(`${library}/logger`);
'use strict';
const api = new API();
const api = global.yoho.API;
/**
* 获取商品的热区
*/
module.exports.indexAsync = (pid) => {
return api.get('', sign.apiSign({
const indexAsync = pid => {
return api.get('', {
method: 'web.productCollocation.list',
product_id: pid
})).catch(log.error);
});
};
module.exports = {
indexAsync
};
... ...
... ... @@ -7,16 +7,13 @@
const Promise = require('bluebird');
const co = Promise.coroutine;
const _ = require('lodash');
const library = '../../../library';
const helpers = require(`${library}/helpers`);
const helpers = global.yoho.helpers;
const api = require('./detail-hotarea-api');
/**
* 获取某一个商品的热区数据
*/
module.exports.indexAsync = (pid) => {
const indexAsync = pid => {
return co(function *() {
let data = yield api.indexAsync(pid);
... ... @@ -80,3 +77,7 @@ module.exports.indexAsync = (pid) => {
})();
};
module.exports = {
indexAsync
};
... ...
'use strict';
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const log = require(`${library}/logger`);
/**
* Created by TaoHuang on 2016/6/14.
*/
const api = new API();
'use strict';
const api = global.yoho.API;
module.exports.getProductBannerAsync = function(pid, clientType) {
const getProductBannerAsync = (pid, clientType) => {
clientType = clientType || 'web';
return api.get('', sign.apiSign({
return api.get('', {
method: 'web.productBanner.data',
product_id: pid,
client_type: clientType
})).catch(log.error);
});
};
module.exports.sizeInfoAsync = function(skn) {
return api.get('', sign.apiSign({
const sizeInfoAsync = skn => {
return api.get('', {
method: 'h5.product.intro',
productskn: skn
})).catch(log.error);
});
};
module.exports.getProductComfortAsync = function(pid) {
return api.get('', sign.apiSign({
const getProductComfortAsync = pid => {
return api.get('', {
method: 'web.productComfort.data',
product_id: pid
})).catch(log.error);
});
};
module.exports.getProductModelCardAsync = function(pid) {
return api.get('', sign.apiSign({
const getProductModelCardAsync = pid => {
return api.get('', {
method: 'web.productModelcard.list',
product_id: pid
})).catch(log.error);
});
};
module.exports.getProductModelTryAsync = function(skn) {
return api.get('', sign.apiSign({
const getProductModelTryAsync = skn => {
return api.get('', {
method: 'web.productModelTry.data',
product_skn: skn
})).catch(log.error);
});
};
/**
... ... @@ -54,14 +51,21 @@ module.exports.getProductModelTryAsync = function(skn) {
* @param pid
* @returns {Promise.<type>}
*/
module.exports.getProductInfo = function(pid) {
return api.get('', sign.apiSign({
const getProductInfo = pid => {
return api.get('', {
method: 'h5.product.data',
productId: pid
})).catch(console.log);
});
};
module.exports = {
getProductBannerAsync,
sizeInfoAsync,
getProductComfortAsync,
getProductModelCardAsync,
getProductModelTryAsync,
getProductInfo
};
... ...
'use strict';
/**
* Created by TaoHuang on 2016/6/14.
*/
const library = '../../../library';
const ServiceAPI = require(`${library}/api`).ServiceAPI;
const sign = require(`${library}/sign`);
const log = require(`${library}/logger`);
'use strict';
const serviceAPI = new ServiceAPI();
const serviceAPI = global.yoho.ServiceAPI;
module.exports.isFavoriteAsync = function(uid, bid) {
return serviceAPI.get('shops/service/v1/favorite/getUidBrandFav', sign.apiSign({
const isFavoriteAsync = (uid, bid) => {
return serviceAPI.get('shops/service/v1/favorite/getUidBrandFav', {
uid: uid,
brandId: bid
})).catch(log.error);
});
};
module.exports.changeAsync = function(uid, brandId) {
return serviceAPI.get('guang/service/v2/favorite/toggleBrand', sign.apiSign({
const changeAsync = (uid, brandId) => {
return serviceAPI.get('guang/service/v2/favorite/toggleBrand', {
uid: uid,
brand_id: brandId
})).catch(log.error);
});
};
module.exports = {
isFavoriteAsync,
changeAsync
};
... ...
... ... @@ -4,9 +4,7 @@
'use strict';
const favoriteBrandAPI = require('./favorite-brand-api');
const api = require('./favorite-brand-api');
module.exports.changeAsync = favoriteBrandAPI.changeAsync;
module.exports.isFavoriteAsync = favoriteBrandAPI.isFavoriteAsync;
module.exports = api;
... ...
... ... @@ -2,36 +2,39 @@
* Created by TaoHuang on 2016/6/13.
*/
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const log = require(`${library}/logger`);
'use strict';
const api = new API();
const api = global.yoho.API;
module.exports.isFavoriteAsync = function(uid, pid) {
return api.get('', sign.apiSign({
const isFavoriteAsync = (uid, pid) => {
return api.get('', {
method: 'app.favorite.isFavorite',
id: pid,
uid: uid,
type: 'product'
})).catch(log.error);
});
};
module.exports.createAsync = function(uid, pid) {
return api.get('', sign.apiSign({
const createAsync = (uid, pid) => {
return api.get('', {
method: 'app.favorite.add',
id: pid,
uid: uid,
type: 'product'
})).catch(log.error);
});
};
module.exports.deleteAsync = function(uid, pid) {
return api.get('', sign.apiSign({
const deleteAsync = (uid, pid) => {
return api.get('', {
method: 'app.favorite.cancel',
fav_id: pid,
uid: uid,
type: 'product'
})).catch(log.error);
});
};
module.exports = {
isFavoriteAsync,
createAsync,
deleteAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/14.
*/
'use strict';
const library = '../../../library';
const helpers = require(`${library}/helpers`);
const helpers = global.yoho.helpers;
/**
* 获取首页频道nav
*/
// index home
module.exports.getHomeChannelNav = function(channel) {
const getHomeChannelNav = (channel) => {
let home = null;
switch (channel) {
... ... @@ -44,3 +47,7 @@ module.exports.getHomeChannelNav = function(channel) {
pathTitle: 'YOHO!有货'
};
};
module.exports = {
getHomeChannelNav
};
... ...
... ... @@ -6,46 +6,54 @@
*/
'use strict';
const library = '../../../library';
const utils = '../../../utils';
const API = require(`${library}/api`).API;
const api = new API();
const api = global.yoho.API;
const saleApi = require('./sale-api');
const outletsApi = require('./outlets-api');
const productProcess = require(`${utils}/product-process`);
const publicHandler = require('./public-handler');
// const headerModel = require('../../../doraemon/models/header');
/**
* 获取商品列表商品数据 Controller 调用
* @param {[type]} params [常规参数]
* @param {[type]} extra [左侧列表额外要拼接的参数]
* @return {[type]} [description]
*/
exports.getSaleOthersData = (params, channel) => {
return api.all([
// headerModel.requestHeaderData(channel),
outletsApi.getOutletsGoodsList(Object.assign(params, { channel: channel })),
outletsApi.getOutletsGoodsList({limit: '1', channel: channel })
]).then(result => {
exports.getListData = (params, channel) => {
let apiArr = [];
if (params.productPool) {
// 奥莱活动页调用app.search.sales
apiArr = [
saleApi.getSaleGoodsList({limit: '1', channel: channel }),
saleApi.getSaleGoodsList(Object.assign(params, { channel: channel }))
];
} else {
// 奥莱品类页调用app.search.li
apiArr = [
outletsApi.getOutletsGoodsList({limit: '1', channel: channel }),
outletsApi.getOutletsGoodsList(Object.assign(params, { channel: channel }))
];
}
return api.all(apiArr).then(result => {
let finalResult = {};
// 获取商品数据和顶部筛选条件
if (result[0].code === 200) {
finalResult.goods = productProcess.processProductList(result[0].data.product_list);
finalResult.leftContent = publicHandler.handleSaleSortData(result[0].data.filter.group_sort, params);
finalResult.pathNav = publicHandler.handlePathNavData(result[0].data.filter.group_sort, params);
}
// 获取左侧类目数据
if (result[1].code === 200) {
finalResult.leftContent = publicHandler.handleSaleSortData(result[1].data.filter.group_sort, params);
finalResult.filters = publicHandler.handleSaleFilterData(result[1].data.filter, params);
finalResult.pathNav = publicHandler.handlePathNavData(result[1].data.filter.group_sort, params);
// 处理排序数据
finalResult.opts = publicHandler.handleSaleOptsData(params, result[1].data.total);
finalResult.totalCount = result[1].data.total;
finalResult.pager = publicHandler.handleSalePagerData(result[1].data.total, params);
Object.assign(finalResult, {
filters: publicHandler.handleSaleFilterData(result[1].data.filter, params),
opts: publicHandler.handleSaleOptsData(params, result[1].data.total),
totalCount: result[1].data.total,
footPager: publicHandler.handlePagerData(result[1].data.total, params),
goods: productProcess.processProductList(result[1].data.product_list)
});
}
return finalResult;
... ...
... ... @@ -6,12 +6,9 @@
*/
'use strict';
const library = '../../../library';
const API = require(`${library}/api`).API;
const ServiceAPI = require(`${global.library}/api`).ServiceAPI;
const serviceApi = new ServiceAPI();
const api = new API();
const sign = require(`${library}/sign`);
const serviceApi = global.yoho.ServiceAPI;
const api = global.yoho.API;
const _ = require('lodash');
const yhChannel = {
... ... @@ -26,6 +23,9 @@ const yhChannel = {
},
lifestyle: {
channel: '4'
},
otltIdxDflt: {
channel: null
}
};
... ... @@ -40,17 +40,16 @@ const yhChannel = {
*/
exports.getOutletsActivityOrigin = (params) => {
let tempChannel = _.isEmpty(params.channel) ? 'boys' : params.channel;
let tempChannel = params.channel || 'boys';
return api.get('', sign.apiSign({
return api.get('', {
method: 'app.outlets.activityGet',
id: params.id || null,
platform: params.platform || 1,
size: params.size || 0,
yh_channel: yhChannel[tempChannel].channel,
type: params.type || 0
}));
});
};
/**
... ... @@ -59,7 +58,7 @@ exports.getOutletsActivityOrigin = (params) => {
* @return {[type]}
*/
exports.getChannelResouceData = (params) => {
return serviceApi.get('operations/api/v5/resource/home', sign.apiSign(params));
return serviceApi.get('operations/api/v5/resource/home', params);
};
/**
... ... @@ -68,15 +67,17 @@ exports.getChannelResouceData = (params) => {
* @return {[type]} [description]
*/
exports.getOutletsTrendData = (params) => {
return api.get('', sign.apiSign({
let tempChannel = params.channel || 'boys';
return api.get('', {
method: 'app.search.trend',
yh_channel: params.yh_channel || '1',
yh_channel: yhChannel[tempChannel].channel,
order: params.order || 's_s_desc,s_n_desc',
gender: params.gender || '1,3',
stocknumber: 1, // 过滤出库存 > 1的商品
limit: params.limit || 5,
outlets: params.outlets || 1 // 默认取奥莱商品
}));
});
};
... ... @@ -85,39 +86,29 @@ exports.getOutletsTrendData = (params) => {
* @return {[type]} [description]
*/
exports.getOutletsGoodsList = (params) => {
// 频道
let tempChannel = params.channel || 'boys';
let tempChannel = _.isEmpty(params.channel) ? 'boys' : params.channel;
// 接口可接收的参数
let apiParams = ['outlets', 'page', 'limit', 'order', 'productSize', 'yh_channel', 'query',
'p_d', 'gender', 'msort', 'misort', 'sort', 'brand', 'color', 'size', 'saleType',
'breakSize', 'breakSort', 'productPool', 'price', 'method'];
// 初始化必填的接口参数
let tempParams = {
method: 'app.search.li',
query: _.isEmpty(params.query) ? null : params.query,
p_d: _.isEmpty(params.p_d) ? null : params.p_d,
limit: _.isEmpty(params.limit) ? '60' : params.limit,
order: _.isEmpty(params.order) ? 's_t_desc' : params.order,
page: _.isEmpty(params.page) ? 1 : params.page,
gender: _.isEmpty(params.gender) ? null : params.gender,
sort: _.isEmpty(params.sort) ? null : params.sort,
msort: _.isEmpty(params.msort) ? null : params.msort,
misort: _.isEmpty(params.misort) ? null : params.misort,
brand: _.isEmpty(params.brand) ? null : params.brand,
color: _.isEmpty(params.color) ? null : params.color,
size: _.isEmpty(params.size) ? null : params.size,
saleType: _.isEmpty(params.saleType) ? null : params.saleType,
breakSize: _.isEmpty(params.breakSize) ? null : params.breakSize,
breakSort: _.isEmpty(params.breakSort) ? null : params.breakSort,
productPool: _.isEmpty(params.productPool) ? null : params.productPool,
price: _.isEmpty(params.price) ? null : params.price,
outlets: _.isEmpty(params.outlets) ? 1 : params.outlets,
outlets: 1,
page: params.page || 1,
limit: params.limit || 60,
order: params.order || 's_t_desc',
productSize: '384x511',
yh_channel: yhChannel[tempChannel].channel
};
let finalParams = {};
_.forEach(tempParams, function(value, key) {
if (!_.isEmpty(value)) {
finalParams[key] = value;
_.forEach(apiParams, (paramsName) => {
if (params[paramsName]) {
tempParams[paramsName] = params[paramsName];
}
});
return api.get('', sign.apiSign(finalParams));
return api.get('', tempParams);
};
... ...
/**
*
* @Author: zhoushaofeng
* @Date: 2016-06-02 15:50:47
* @Last Modified time: 2016-06-08 19:31:52
*/
'use strict';
const library = '../../../library';
const utils = '../../../utils';
const helpers = require(`${library}/helpers`);
const helpers = global.yoho.helpers;
const _ = require('lodash');
const camelCase = require('../../../library/camel-case');
const productProcess = require(`${utils}/product-process`);
... ... @@ -9,33 +14,6 @@ const url = require('url');
const queryString = require('querystring');
/**
* NL2R图片轮播
*/
const nl2r = (data) => {
const result = {
left: [],
right: []
};
// 左侧
_.forEach(data.left, (value) => {
result.left.push({
img: value.src,
url: value.url
});
});
// 右侧
_.forEach(data.right, (value) => {
result.right.push({
img: value.src,
url: value.url
});
});
return result;
};
/**
* 热门分类
*/
const hotCategory = (data) => {
... ... @@ -76,17 +54,17 @@ const discountSplit = (text) => {
* @return {[type]} [description]
*/
const handleOutletsGoodsMenuData = (origin, params) => {
let dest = {};
var oldParam = _.cloneDeep(params);
dest.title = '最新折扣';
dest.more = '/product/outlets/list';
dest.menuList = [];
let dest = {
title: '最新折扣',
more: '/product/outlets/list',
menuList: [],
msort: [],
misort: []
};
_.forEach(origin, subValue => {
let oldParam = _.cloneDeep(params);
let goodsmenu = {};
let urlSuffix = {};
goodsmenu.name = subValue.categoryName;
... ... @@ -96,22 +74,24 @@ const handleOutletsGoodsMenuData = (origin, params) => {
if (!_.isEmpty(urlSuffix.msort) && urlSuffix.msort === oldParam.msort && urlSuffix.misort === oldParam.misort) {
goodsmenu.cur = true;
}
delete oldParam.msort;
delete oldParam.misort;
if (urlSuffix.msort) {
Object.assign(params, {msort: urlSuffix.msort});
Object.assign(oldParam, {msort: urlSuffix.msort});
dest.msort.push(urlSuffix.msort);
}
if (urlSuffix.misort) {
Object.assign(params, {misort: urlSuffix.misort});
Object.assign(oldParam, {misort: urlSuffix.misort});
dest.misort.push(urlSuffix.misort);
}
goodsmenu.href = '?' + queryString.stringify(params);
goodsmenu.href = '?' + queryString.stringify(oldParam) + '#otspool';
dest.menuList.push(goodsmenu);
});
return dest;
};
/**
* 分类导航
*/
... ... @@ -138,7 +118,7 @@ exports.processFloor = (list, params) => {
_.forEach(list, (floor) => {
switch (floor.templateName) {
case 'NL2R':
floorData = nl2r(floor.data);
floorData = {topBanner: floor.data};
break;
case 'hotCategory':
floorData = hotCategory(floor.data);
... ... @@ -171,8 +151,9 @@ exports.handleOutletsBannersData = (origin, params) => {
// 处理焦点图数据
if (value.template_name === 'focus') {
dest.mainBanner = {};
dest.mainBanner.list = value.data;
dest.mainBanner = {
list: value.data
};
}
// 处理三张小图数据
... ... @@ -182,17 +163,18 @@ exports.handleOutletsBannersData = (origin, params) => {
// 处理右侧一张图片
if (value.template_name === 'single_image') {
dest.limitedBuy.extra.sourceImg = {};
dest.limitedBuy.extra.sourceImg.href = value.data[0].url;
dest.limitedBuy.extra.sourceImg.img = value.data[0].src;
console.log(value.data[0].src);
dest.limitedBuy.extra.sourceImg = {
href: value.data[0].url,
img: value.data[0].src
};
}
// 处理热销推荐数据
if (value.template_name === 'recommendCategory') {
dest.limitedBuy.extra.hotType = {};
dest.limitedBuy.extra.hotType.title = _.isEmpty(value.data.title) ? '热销推荐' : value.data.title;
dest.limitedBuy.extra.hotType.classify = [];
dest.limitedBuy.extra.hotType = {
title: _.isEmpty(value.data.title) ? '热销推荐' : value.data.title,
classify: []
};
_.forEach(value.data.categoryList, subValue => {
let category = {};
... ... @@ -208,9 +190,9 @@ exports.handleOutletsBannersData = (origin, params) => {
// 处理品类导航数据
if (value.template_name === 'categoryNavigation') {
dest.goodsBoard = {};
dest.goodsBoard.goodsMenu = handleOutletsGoodsMenuData(value.data, params);
dest.goodsBoard = {
goodsMenu: handleOutletsGoodsMenuData(value.data, params)
};
}
});
... ... @@ -224,23 +206,20 @@ exports.handleOutletsBannersData = (origin, params) => {
* @return {[type]} [description]
*/
exports.handleOutletsActivityData = (origin, name) => {
let dest = {};
let nowTime = Math.round(new Date().getTime() / 1000);
dest.name = name || '限时嗨购'; // 需要根据 name 传值修改
dest.topic = [];
let dest = {
name: name || '限时嗨购',
topic: []
};
// 处理奥莱活动列表数据
_.forEach(origin, value => {
let activity = {};
activity.href = helpers.urlFormat('/product/outlets/special/detail', { id: value.id });
activity.img = value.webCoverUrl;
activity.logo = value.logoUrl;
activity.title = value.title;
activity.limit = (value.endTime - nowTime) * 1000;
let activity = {
href: helpers.urlFormat('/product/outlets/special/detail', {id: value.id}),
img: value.webCoverUrl,
logo: value.logoUrl,
title: value.title,
limit: parseInt(value.endLeftTime, 10) * 1000
};
if (value.promotionName) {
Object.assign(activity, discountSplit(value.promotionName));
... ... @@ -261,7 +240,7 @@ exports.handleOutletsSpecilData = (origin) => {
let dest = {};
dest.mainBanner = {
src: origin.webUrl
src: helpers.image(origin.webUrl, 1920, 360)
};
dest.specialHead = {
... ... @@ -289,7 +268,27 @@ exports.handleOutletstrendGoodData = (origin) => {
dest.title = '潮品推荐';
dest.goods = productProcess.processProductList(origin.product_list);
_.forEach(dest.goods, (value, key) => {
dest.goods[key].discount = (value.salesPrice / value.marketPrice).toFixed(2) * 10;
dest.goods[key].discount = (value.salesPrice / value.marketPrice * 10).toFixed(1) * 1;
});
return dest;
};
/**
* 处理即将上线品牌数据
* @param origin
* @returns {{}}
*/
exports.handleComeSoonData = (origin) => {
let dest = {
title: '上线预告'
};
dest.brands = [];
_.forEach(origin, value => {
dest.brands.push({
logo: value.logoUrl
});
});
return dest;
... ...