Authored by 梁志锋

merge 1.0

Showing 55 changed files with 3410 additions and 787 deletions

Too many changes to show.

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

... ... @@ -2,12 +2,12 @@
# Created by https://www.gitignore.io/api/node,webstorm,netbeans,sublimetext,vim
### Node ###
# Logs
!logs/README.md
# Logs
!logs/README.md
logs
*.log
npm-debug.log*
npm-debug.log*
# Runtime data
pids
*.pid
... ... @@ -141,3 +141,4 @@ public/bundle/*
.eslintcache
*.log.*
.vscode/
.DS_Store
... ...
**/css/**/*.css
**/dist/**/*.css
public/scss/common/_slider.css
... ...
... ... @@ -22,11 +22,14 @@ const memcached = require('connect-memcached');
const hbs = require('express-handlebars');
const pkg = require('./package.json');
const yohoLib = require('yoho-node-lib');
const app = express();
const MemcachedStore = memcached(session);
// 指定libray目录
global.library = path.resolve('./library');
// 全局注册library
yohoLib.global(config);
global.middleware = path.resolve('./doraemon/middleware');
global.utils = path.resolve('./utils');
... ... @@ -41,7 +44,7 @@ app.engine('.hbs', hbs({
defaultLayout: 'layout',
layoutsDir: './doraemon/views',
partialsDir: './doraemon/views/partial',
helpers: require(`${global.library}/helpers`)
helpers: global.yoho.helpers
}));
app.use(favicon(path.join(__dirname, '/public/favicon.ico')));
... ... @@ -74,19 +77,23 @@ app.use((req, res, next) => {
next();
});
const logger = require('./library/logger');
const logger = global.yoho.logger;
// dispatcher
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);
... ...
... ... @@ -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('activity/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)]);
};
... ...
... ... @@ -10,7 +10,7 @@ const _ = require('lodash');
const channelModel = require('../models/index');
exports.index = (req, res) => {
exports.index = (req, res, next) => {
let channelType = req.path.substring(1) || 'boys';
// 将woman转换为girls,以便model层进行处理
... ... @@ -18,18 +18,18 @@ exports.index = (req, res) => {
channelModel.getContent(channelType).then(data => {
res.render('channel', data);
});
}).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,6 +54,22 @@ exports.getNewArrival = (req, res) => {
};
}
res.send(result);
});
}).catch(next);
};
exports.getIndexGuide = (req, res,next) => {
channelModel.getIndexGuideData().then(data => {
if(data.code !== 200){
const err = new Error('异常');
throw err;
}
return channelModel.formatIndexGuideData(data.data);
}).then(data => {
return channelModel.getResourceData(data);
}).then(data => {
console.log(data);
data.layout = false;
res.render('guide', {list: data});
}).catch(next);
}
... ...
... ... @@ -25,8 +25,8 @@ app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [`${partials}/partials`, `${doraemon}/partial`],
helpers: require('../../library/helpers')
partialsDir: [`${partials}/partial`, `${doraemon}/partial`],
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 = [];
const channelMap = dataMap.channel;
_.forEach(navs, it => {
let obj = {};
obj.name = it.name;
obj.href = it.url;
list.push(obj);
});
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,195 @@ const processFloorData = (rawData, type) => {
return {
floors: floorList,
promise: _.reverse(searchPromise),
singlehotFloorTitile: _.reverse(singlehotFloorTitile),
singlehotFloorTitle: _.reverse(singlehotFloorTitle),
singlehotFloorIndex: _.reverse(singlehotFloorIndex)
};
};
const _formatResourceParams = (channel, code) => {
return serviceApi.get('operations/api/v5/resource/get', {content_code: code}).then(data => {
let result = data && data.data[0] && data.data[0].data[0];
if (result) {
result.channel = channel;
}
return result || {};
});
};
/**
* 频道页首次登陆导航并行调接口数据
* @param {Object} data
* @return {Object} formatData
*/
const _formatParams = (channel, data) => {
let params = {};
// 排序数据映射表
let orderMaps = {
s_t_desc: 'shelve_time:desc',
s_t_asc: 'shelve_time:asc',
s_p_asc: 'sales_price:asc',
s_p_desc: 'sales_price:desc',
p_d_desc: 'discount:desc',
p_d_asc: 'discount:asc',
skn_desc: 'product_skn:desc',
skn_asc: 'product_skn:asc',
activities_desc: 'activities.order_by:desc',
activities_asc: 'activities.order_by:asc',
s_n_asc: 'sales_num:asc',
s_n_desc: 'sales_num:desc',
activities_id_desc: 'activities.activity_id:desc',
activities_id_asc: 'activities.activity_id:asc',
brand_desc: 'brand_weight:desc'
};
params.status = 1; // 是否上架,1表示在架,2表示不在
params.sales = 'Y'; // 只搜索销售的产品
params.outlets = 2; // 非奥莱商品
params.stocknumber = 1; // 过滤掉已售罄的商品
params.attribute_not = 2; // 过滤掉赠品
if (!data.order) {
params.order = orderMaps.s_t_desc;
} else {
params.order = orderMaps[data.order] ? orderMaps[data.order] : '';
}
if (!data.page) {
params.page = 1;
}
if (data.viewNum) {
params.viewNum = data.viewNum;
} else if (!data.limit) {
params.viewNum = 60;
} else {
params.viewNum = data.limit;
delete data.limit;
}
if (data) {
params = Object.assign(data);
}
return searchApi.get('/search.json', params).then(result => {
let ret = result.data;
ret.channel = channel;
return ret;
});
};
/**
* 格式化频道页首次登陆导航数据
* @param {Object} data
* @return {Object} formatData
*/
const formatIndexGuideData = data => {
let channels = {
boys: {gender: '1,3', limit: 1},
girls: {gender: '2,3', limit: 1},
lifestyle: {msort: 10, misort: '266,280,101,103,259', limit: 1},
kids: {msort: 365, limit: 1}
};
let formatData = [];
let promiseArr = [];
_.forEach(data, item => {
let channel = item.sort_name_en.toLowerCase().replace(' ', '');
formatData.push({
channel: channel,
url: item.sort_url,
sort_name: item.sort_name,
sort_name_en: item.sort_name_en,
content_code: item.content_code,
src: '',
num: 0
});
if (channels[channel]) {
promiseArr.push(_formatParams(channel, channels[channel]));
}
});
return Promise.all(promiseArr).then(result => {
_.forEach(formatData, (item, index) => {
_.forEach(result, (it, idx) => {
if (formatData[index].channel === result[idx].channel) {
formatData[index].num = result[idx].total;
}
});
});
return formatData;
});
};
/**
* 获取最新上架商品数据
*
* @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 +786,7 @@ exports.getContent = type => {
floorData: data,
searchPromise: processResult.promise,
singlehotFloorIndex: processResult.singlehotFloorIndex,
singlehotFloorTitile: processResult.singlehotFloorTitile,
singlehotFloorTitle: processResult.singlehotFloorTitle,
channelType: type
};
... ... @@ -933,8 +798,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 +816,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 +843,48 @@ exports.getbrandFloorDataAjax = type => {
return data;
});
};
const getResourceData = (formatData) => {
let promiseArr = [];
_.forEach(formatData, item => {
if (item.content_code) {
promiseArr.push(_formatResourceParams(item.channel, item.content_code));
}
});
return Promise.all(promiseArr).then(data => {
_.forEach(formatData, (item, index) => {
_.forEach(data, (it, idx) => {
if (formatData[index].channel === data[idx].channel) {
formatData[index].src = data[idx].src;
}
});
});
return formatData;
});
};
/**
* 获取频道页首次登陆导航数据
* @param {String} type 传入频道页类型,值可以是: boys, girls, kids, lifestyle
* @return {Object}
*/
const getIndexGuideData = () => {
let params = {
client_type: 'web',
private_key: '0ed29744ed318fd28d2c07985d3ba633'
};
return serviceApi.get('operations/api/v6/category/getCategory', params);
};
module.exports = {
getNewArrival: getNewArrival,
getContent: getContent,
getbrandFloorDataAjax: getbrandFloorDataAjax,
getIndexGuideData: getIndexGuideData,
formatIndexGuideData: formatIndexGuideData,
getResourceData: getResourceData
};
... ...
... ... @@ -21,5 +21,6 @@ router.get('/lifestyle', channelController.index);
// ajax
router.get('/getbrandFloorDataAjax', channelController.getbrandFloorDataAjax);
router.post('/common/getNewArrival', channelController.getNewArrival);
router.get('/guide', channelController.getIndexGuide);
module.exports = router;
... ...
<div class="con"></div>
<div class="guide-box" spm-name="homepage_guide" style="top: 255.5px;">
<a class="close" href="javascript:void(0)">x</a>
<ul class="clear">
{{#list}}
<li class="{{channel}}" >
<dl>
<dt class="tag_img_warpper">
<a href="{{url}}" target="_self">
<img spm-name="homepage_guide_manimg" alt="YOHO!BOYS" src="{{image src 180 168}}"></a>
</dt>
<dd class="block_cn">{{sort_name}}</dd>
<dd class="block_en"> <b>{{sort_name_en}}</b>
</dd>
<dd class="block_line">
</dd>
<dd class="goods-num"> <b>{{num}}+ Items.</b>
</dd>
<dd>
<a class="go" href="{{url}}">
Enter
<span class="ifont">&gt;</span>
</a>
</dd>
</dl>
</li>
{{/list}}
</ul>
</div>
... ...
<div class="floor-ad">
<a href="{{href}}" target= "_blank"><img class="lazy" data-original="{{image img 1150 129}}"/></a>
</div>
<div class="floor-ad">
<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">
<img class="lazy" data-original="{{image img 200 265}}">
</a>
{{/ left}}
</div>
<div class="center-col col">
<ul class="slide-wrapper">
{{# center}}
<li>
<a href="{{href}}" target="_blank">
<img class="lazy" data-original="{{image img 570 633}}">
</a>
</li>
{{/ center}}
</ul>
<div class="slide-switch">
<a class="prev" href="javascript:;">
<span class="iconfont">&#xe60c;</span>
</a>
<a class="next" href="javascript:;">
<span class="iconfont">&#xe60b;</span>
</a>
</div>
</div>
<div class="right-col col">
{{# right}}
<a href="{{href}}" target="_blank">
<img class="lazy" data-original="{{image img 200 265}}">
</a>
{{/ right}}
</div>
</div>
<div class="debris-slider clearfix">
<div class="left-col col">
{{# left}}
<a href="{{url}}" target="_blank">
<img class="lazy" data-original="{{image img 200 265}}">
</a>
{{/ left}}
</div>
<div class="center-col col">
<ul class="slide-wrapper">
{{# center}}
<li>
<a href="{{url}}" target="_blank">
<img class="lazy" data-original="{{image img 570 633}}">
</a>
</li>
{{/ center}}
</ul>
<div class="slide-switch">
<a class="prev" href="javascript:;">
<span class="iconfont">&#xe609;</span>
</a>
<a class="next" href="javascript:;">
<span class="iconfont">&#xe608;</span>
</a>
</div>
</div>
<div class="right-col col">
{{# right}}
<a href="{{url}}" target="_blank">
<img class="lazy" data-original="{{image img 200 265}}">
</a>
{{/ right}}
</div>
</div>
... ...
<div class="preference-brand">
<div class="img-brand">
<ul class="img-list imgopacity clearfix">
{{# imgBrand}}
<li class="img-item">
<a href="{{href}}" target= "_blank">
<img src="{{image img 378 175}}" alt="">
</a>
</li>
{{/ imgBrand}}
</ul>
<div class="img-brand-switch">
<a class="prev" href="javascript:;">
<span class="iconfont">&#xe60c;</span>
</a>
<a class="next" href="javascript:;">
<span class="iconfont">&#xe60b;</span>
</a>
</div>
</div>
<div class="logo-brand imgopacity" data-url="{{brandUrl}}"></div>
</div>
<div class="preference-brand">
<div class="img-brand">
<ul class="img-list imgopacity clearfix">
{{# imgBrand}}
<li class="img-item">
<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">&#xe609;</span>
</a>
<a class="next" href="javascript:;">
<span class="iconfont">&#xe608;</span>
</a>
</div>
</div>
<div class="logo-brand imgopacity" data-url="{{brandUrl}}"></div>
</div>
... ...
{{# tplrecommend}}
<div class="tpl-recommend clearfix">
{{> common/floor-header}}
<div class="tpl-body clearfix">
<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>
{{/each}}
</div>
<div class="tpl-category clearfix">
{{#each category}}
<a href="{{href}}" target= "_blank">{{name}}</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>
{{/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>
{{/each}}
</ul>
</div>
</div>
<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>
{{/each}}
</ul>
</div>
</div>
{{/ tplrecommend}}
{{# tplrecommend}}
<div class="tpl-recommend clearfix">
{{> common/floor-header}}
<div class="tpl-body clearfix">
<div class="tpl-nav">
<div class="tpl-keywords">
{{#each keyword}}
<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="{{url}}" target= "_blank">{{title}}</a>
{{/each}}
</div>
</div>
<div class="tpl-brands imgopacity clearfix">
<ul>
{{#each brands}}
<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="{{title}}" href="{{url}}" target= "_blank"><img class="lazy" src="{{image img 185 248}}"/></a></li>
{{/each}}
</ul>
</div>
</div>
<div class="tpl-products imgopacity clearfix">
<ul>
{{#each products}}
<li><a href="{{url}}" title="{{title}}" target= "_blank"><img class="lazy" src="{{image src 222 298}}"/></a></li>
{{/each}}
</ul>
</div>
</div>
{{/ tplrecommend}}
... ...
... ... @@ -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>
... ...
{{> common/floor-header}}
<div class="categorys-list imgopacity">
<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>
</li>
{{/ list}}
</ul>
</div>
{{> common/floor-header}}
<div class="categorys-list imgopacity">
<ul class="clearfix">
{{# list}}
<li class="cate-item{{@index}}">
<a href="{{url}}" target= "_blank">
<img class="lazy" data-original="{{image src w h}}" alt="">
</a>
</li>
{{/ list}}
</ul>
</div>
... ...
<div class="commodity clearfix" id="newarrivals">
{{> common/floor-header}}
<div class="goods-container clearfix">
</div>
<div class="loading">
<a href="{{href}}" target= "_blank">Loading...</a>
</div>
</div>
<div class="commodity clearfix" id="newarrivals">
{{> common/floor-header}}
<div class="goods-container clearfix">
</div>
<div class="loading">
<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>
</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>
</li>
{{/ imgBrand}}
</ul>
</div>
</div>
<div class="logo-brand " data-url="{{brandUrl}}">
</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:;">&#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="{{url}}" target= "_blank"> <img src="{{image src 320 430}}" alt="{{alt}}"></a>
</li>
{{/ imgBrand}}
</ul>
</div>
</div>
<div class="logo-brand " data-url="{{brandUrl}}">
</div>
</div>
... ...
{{# tplrecommend}}
<div class="tpl-recommend clearfix">
{{> common/floor-header}}
<div class="tpl-body clearfix">
<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>
{{/each}}
</div>
<div class="tpl-category clearfix">
{{#each category}}
<a href="{{href}}" target= "_blank">{{name}}</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>
{{/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>
{{/each}}
</ul>
</div>
</div>
</div>
{{/ tplrecommend}}
{{# tplrecommend}}
<div class="tpl-recommend clearfix">
{{> common/floor-header}}
<div class="tpl-body clearfix">
<div class="tpl-nav">
<div class="tpl-keywords">
{{#each keyword}}
<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="{{url}}" target= "_blank">{{title}}</a>
{{/each}}
</div>
</div>
<div class="tpl-brands imgopacity clearfix">
{{#each brands}}
<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="{{title}}" href="{{url}}" target= "_blank"><img class="lazy" data-original="{{image img 185 504}}"/></a></li>
{{/each}}
</ul>
</div>
</div>
</div>
{{/ tplrecommend}}
... ...
... ... @@ -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>
... ...
<div class="new-report imgopacity clearfix">
{{> common/floor-header}}
<ul class="report-list clearfix">
{{# list}}
{{#unless @last}}
<li>
<a href="{{href}}" target= "_blank">
{{#if @first}}
<img class="lazy" data-original="{{image img 377 504}}" alt="" >
{{^}}
<img class="lazy" data-original="{{image img 185 248}}" alt="" >
{{/if}}
</a>
</li>
{{/unless}}
{{/ list}}
</ul>
{{# list}}
{{#if @last}}
<div class="last-item">
<a href="{{href}}" target= "_blank">
<img class="lazy" data-original="{{image img 377 504}}" alt="">
</a>
</div>
{{/if}}
{{/ list}}
</div>
<div class="new-report imgopacity clearfix">
{{> common/floor-header}}
<ul class="report-list clearfix">
{{# list}}
{{#unless @last}}
<li>
<a href="{{url}}" target= "_blank">
{{#if @first}}
<img class="lazy" data-original="{{image src 377 504}}" alt="" >
{{^}}
<img class="lazy" data-original="{{image src 185 248}}" alt="" >
{{/if}}
</a>
</li>
{{/unless}}
{{/ list}}
</ul>
{{# list}}
{{#if @last}}
<div class="last-item">
<a href="{{url}}" target= "_blank">
<img class="lazy" data-original="{{image src 377 504}}" alt="">
</a>
</div>
{{/if}}
{{/ list}}
</div>
... ...
{{> common/floor-header}}
<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>
{{/each}}
</ul>
</div>
{{> common/floor-header}}
<div class="slide-accordion clearfix">
<ul>
{{#each slide}}
<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>
... ...
... ... @@ -5,13 +5,120 @@
*/
'use strict';
const _ = require('lodash');
const passport = require('passport');
const WeixinStrategy = require('passport-weixin');
const SinaStrategy = require('passport-sina').Strategy;
const LocalStrategy = require('passport-local').Strategy;
const QQStrategy = require('passport-qq').Strategy;
const AlipayStrategy = require('./models/passport-alipay').Strategy;
const config = require('../../config/common');
const md5 = require('md5');
const AuthHelper = require('./models/auth-helper');
const config = global.yoho.config;
const helpers = global.yoho.helpers;
const cookie = global.yoho.cookie;
const logger = global.yoho.logger;
const cache = global.yoho.cache;
let siteUrl = config.siteUrl.indexOf('//') === 0 ? 'http:' + config.siteUrl : config.siteUrl;
// 本地登录
passport.use(new LocalStrategy({
usernameField: 'account',
passwordField: 'password',
passReqToCallback: true
}, (req, username, password, done) => {
let area = req.body.area || '86';
if (isNaN(parseInt(area, 0)) || _.isEmpty(username) || _.isEmpty(password)) {
logger.info(`【Passport Loginbad params, area:${area} account:${username} password:${password}`);
return done('登录参数错误', null);
}
let verifyEmail = helpers.verifyEmail(username);
let verifyMobile = helpers.verifyAreaMobile(username, area);
if (!verifyEmail && !verifyMobile) {
logger.info(`【Passport Loginbad account, email:${verifyEmail} mobile:${verifyMobile}`);
return done('登录账号格式错误', null);
}
let expire = req.cookies['LE' + md5('_LOGIN_EXPIRE')];
if (_.isEmpty(expire) || expire < (new Date()).getTime() / 1000) {
return done('页面停留时间过长,请刷新页面', null);
}
let shoppingKey = cookie.getShoppingKey(req);
let account = req.body.account;
let ip = req.ip;
let errorLoginKey = 'account_errorlogin_' + account;
let accountKey = 'account_signin_' + account;
let ipKey = 'ip_signin_' + ip;
let cacheGet = [cache.get(errorLoginKey), cache.get(accountKey), cache.get(ipKey)];
Promise.all(cacheGet).then(times => {
let errLoginTimes = parseInt(times[0], 0) || 0;
let accountTimes = parseInt(times[1], 0) || 0;
let ipTimes = parseInt(times[2], 0) || 0;
console.log(errLoginTimes);
if (accountTimes >= 10) {
done({ message: '您的账号已被暂时锁定,请稍后再试' }, null);
} else if (ipTimes >= 100) {
done({ message: '您尝试的次数过多,账号已被暂时锁定,请稍后再试' }, null);
} else {
return AuthHelper.signin(area, username, password, shoppingKey).then((result) => {
console.log(result);
if (result.code && result.code === 200 && result.data.uid) {
cache.del(errorLoginKey);
done(null, result.data);
} else {
errLoginTimes = errLoginTimes + 1;
accountTimes = accountTimes + 1;
ipTimes = ipTimes + 1;
cache.set(errorLoginKey, errLoginTimes);
cache.set(accountKey, accountTimes, 1800);
cache.set(ipKey, ipTimes, 3600);
// 再次校验
if (ipTimes >= 100) {
done({ message: '您尝试的次数过多,账号已被暂时锁定,请稍后再试' }, null);
} else if (accountTimes >= 10) {
done({ message: '您的账号已被暂时锁定,请稍后再试' }, null);
} else if (errLoginTimes >= 3) {
done({
message: `您输入的密码及账户名不匹配,
是否<a href="${helpers.urlFormat('/passport/back/index')}" target="_blank">忘记密码?</a>`,
needCaptcha: true
});
} else {
done({
message: `您输入的密码及账户名不匹配,
是否<a href="${helpers.urlFormat('/passport/back/index')}" target="_blank">忘记密码?</a>`,
needCaptcha: false
});
}
}
});
}
}).catch(e => {
logger.error('call the signin service fail,', e);
done('登录失败,请稍后重试', null);
});
}));
/**
* wechat登录
*/
... ... @@ -22,6 +129,35 @@ passport.use('wechat', new WeixinStrategy({
callbackURL: `${siteUrl}/passport/login/wechat/callback`,
requireState: true,
scope: 'snsapi_login'
}, function(accessToken, refreshToken, profile, done) {
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
// sina 登录
passport.use('sina', new SinaStrategy({
clientID: '3739328910',
clientSecret: '9d44cded26d048e23089e5e975c93df1',
callbackURL: `${siteUrl}/passport/login/sina/callback`,
requireState: false
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
// qq 登录
passport.use('qq', new QQStrategy({
clientID: '100229394',
clientSecret: 'c0af9c29e0900813028c2ccb42021792',
callbackURL: `${siteUrl}/passport/login/qq/callback`,
requireState: false
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
// alipay 登录
passport.use('alipay', new AlipayStrategy({
partner: '2088701661478015',
key: 'kcxawi9bb07mzh0aq2wcirsf9znusobw',
callbackURL: `${siteUrl}/passport/login/alipay/callback`
}), (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');
/**
* 找回密码主页面
*/
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 => {
console.log(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) => {
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 validateSuccessStatusPage = (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 ERR = {
code: 400,
message: '验证码错误!',
data: helpers.urlFormat('/passport/back/index')
};
const session = req.session;
if (!mobileCode || 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, // 首页
getUserInfoAPI, // 通过邮箱或手机号获得用户信息
sendCodePage, // 发送验证码到邮箱或者手机,然后跳转页面
saveInSession, // 保存状态到session中
sendEmailPage, // 发送邮件成功的页面
verifyCodeByMobilePage, // 验证手机验证码的页面
verifyCodeByMobileAPI, // 验证手机验证码
sendBackMobileAPI, // 重新发送验证码到手机
resetPasswordPage, // 重设密码页面
updatePwdAPI, // 重设密码接口
validateMobileAPI, // 验证手机号是否合法
resetPwdSuccessPage, // 重设密码成功页面
validateInputAPI, // 验证用户输入的邮箱或者手机是否合法,返回是json
validateUserPage, // 验证用户输入的邮箱或者手机是否合法,跳转是页面
validateEmailInSession, // 验证邮箱是否在session中
validateMobileInSession, // 验证手机是否在session中
validateCodeByEmailPage, // 验证邮箱验证码
validateCodeByMobilePage, // 验证手机验证码
validateSuccessStatusPage, // 验证重设密码状态
validateExistCodePage, // 验证参数是否存在code
validatePwdPage // 验证密码是否合法
};
... ...
/**
* 第三方登录后绑定
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
*/
'use strict';
const bind = {
};
module.exports = bind;
... ...
/**
* 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
};
... ...
... ... @@ -5,19 +5,23 @@
*/
'use strict';
const library = '../../../library';
const _ = require('lodash');
const passport = require('passport');
const uuid = require('uuid');
const cookie = require(`${library}/cookie`);
const helpers = require(`${library}/helpers`);
const log = require(`${library}/logger`);
const config = require('../../../config/common');
const md5 = require('md5');
const cookie = global.yoho.cookie;
const helpers = global.yoho.helpers;
const log = global.yoho.logger;
const config = global.yoho.config;
const cache = global.yoho.cache;
const AuthHelper = require('../models/auth-helper');
const PassportHelper = require('../models/passport-helper');
const loginPage = `${config.siteUrl}/passport/login/index`;
const SIGNIN_LEFT_BANNER_CODE = 'db350894e01e90eac55cd3a13ad77331';
// 第三方登录回调
function doPassportCallback(req, res, next, user) {
function doPassportCallback(req, res, user) {
let shoppingKey = cookie.getShoppingKey(req);
let refer = req.cookies.refer;
... ... @@ -56,7 +60,7 @@ function doPassportCallback(req, res, next, user) {
} 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);
... ... @@ -68,7 +72,7 @@ function doPassportCallback(req, res, next, user) {
}
}
const wechat = {
const common = {
beforeLogin: (req, res, next) => {
let refer = req.query.refer;
... ... @@ -80,27 +84,247 @@ const wechat = {
});
next();
},
needCaptcha: (req, res, next) => {
let account = req.query.account;
let result = {code: 400, message: '', data: ''};
if (account) {
let errorLoginKey = 'account_errorlogin_' + account;
cache.get(errorLoginKey).then(errloginTimes => {
errloginTimes = parseInt(errloginTimes, 0) || 0;
console.log(errloginTimes);
if (!isNaN(errloginTimes) && errloginTimes >= 3) {
result.data = {needCaptcha: true};
}
res.json(result);
}).catch(next);
} else {
res.json(result);
}
}
};
const local = {
loginPage: (req, res) => {
// 设置登录有效时间30分钟, 防机器刷,cache不稳定,改为cookie
res.cookie('LE' + md5('_LOGIN_EXPIRE'), (new Date()).getTime() / 1000 + 1800);
let bindMobile = _.trim(req.query.bindMobile || '');
let bindArea = '+' + _.trim(req.query.bindArea || '86');
let areaArr = PassportHelper.getCountry();
let areaName = '';
if (bindArea) {
let area = areaArr.find((a) => {
return a.areaCode === bindArea;
});
areaName = area ? area.name : '';
}
PassportHelper.getLeftBannerAsync(SIGNIN_LEFT_BANNER_CODE).then(cover => {
res.render('login', {
loginPage: true,
passport: {
coverHref: cover.url,
coverImg: cover.img,
countryCode: bindArea,
countryName: areaName,
countryList: areaArr,
forgetPwd: helpers.urlFormat('/passport/back/index'),
fastReg: helpers.urlFormat('/reg.html'),
weixinLogin: helpers.urlFormat('/passport/autosign/wechat'),
qqLogin: helpers.urlFormat('/passport/autosign/qq'),
weiboLogin: helpers.urlFormat('/passport/autosign/sina'),
alipayLogin: helpers.urlFormat('/passport/autosign/alipay'),
doubanLogin: helpers.urlFormat('/passport/autosign/douban'),
renrenLogin: helpers.urlFormat('/passport/autosign/renren'),
bindMobile: bindMobile
},
module: 'passport',
page: 'login',
title: '用户登录'
});
});
},
login: (req, res, next) => {
passport.authenticate('local', (err, user) => {
if (err) {
res.json({
code: 400,
message: err.message,
data: {
needCaptcha: err.needCaptcha
}
});
} else {
let isRemember = req.body.isRemember;
let refer = req.cookies.refer;
if (isRemember) {
AuthHelper.rememberAccount({
area: req.body.areaCode || '86',
account: req.body.account,
password: req.body.password
}, req, res);
}
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = `${config.siteUrl}/home`;
}
if (/sign|login/.test(refer)) {
refer = `${config.siteUrl}/home`;
}
user.session = refer;
user.href = refer;
AuthHelper.syncUserSession(user.uid, req, res).then(() => {
res.json({
code: 200,
data: user
});
});
}
})(req, res, next);
},
logout: (req, res) => {
req.session = null;
res.clearCookie('_UID', {
domain: 'yohobuy.com'
});
res.clearCookie('_TOKEN', {
domain: 'yohobuy.com'
});
res.clearCookie('_SPK');
res.clearCookie('_g');
res.clearCookie('isRemember');
res.clearCookie('remem');
let refer = req.get('Referer') || config.siteUrl;
res.redirect(refer);
}
};
const wechat = {
login: (req, res, next) => {
req.session = req.session || {};
req.session.authState = uuid.v4();
return passport.authenticate('wechat', {
state: uuid.v4()
state: req.session.authState
})(req, res, next);
},
callback: (req, res, next) => {
passport.authenticate('wechat', (err, user) => {
if (req.session && req.session.authState && req.session.authState === req.query.state) {
passport.authenticate('wechat', (err, user) => {
if (err) {
log.error(`wechat authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
doPassportCallback(req, res, {
openId: user._json.openid,
unionId: user._json.unionid || user.id,
nickname: user._json.nickname || user.displayName,
sourceType: 'wechat',
rawUser: user
}).catch(next);
})(req, res, next);
} else {
return next(new Error('Auth State Mismatch'));
}
}
};
const sina = {
login: (req, res, next) => {
req.session = req.session || {};
req.session.authState = uuid.v4();
return passport.authenticate('sina', {
state: req.session.authState
})(req, res, next);
},
callback: (req, res, next) => {
if (req.session && req.session.authState && req.session.authState === req.query.state) {
passport.authenticate('sina', (err, user) => {
if (err) {
log.error(`sina authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.screen_name;
let openId = user.id;
doPassportCallback(req, res, {
openId: openId,
nickname: nickname,
sourceType: 'sina'
}).catch(next);
})(req, res, next);
} else {
return next(new Error('Auth State Mismatch'));
}
}
};
const qq = {
login: (req, res, next) => {
req.session = req.session || {};
req.session.authState = uuid.v4();
return passport.authenticate('qq', {
state: req.session.authState
})(req, res, next);
},
callback: (req, res, next) => {
if (req.session && req.session.authState && req.session.authState === req.query.state) {
passport.authenticate('qq', (err, user) => {
if (err) {
log.error(`qq authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.nickname;
let openId = user.openid;
doPassportCallback(req, res, {
openId: openId,
nickname: nickname,
sourceType: 'qq'
}).catch(next);
})(req, res, next);
} else {
return next(new Error('Auth State Mismatch'));
}
}
};
const alipay = {
login: (req, res, next) => {
return passport.authenticate('alipay')(req, res, next);
},
callback: (req, res, next) => {
passport.authenticate('alipay', (err, user) => {
if (err) {
log.error(`wechat authenticate error : ${JSON.stringify(err)}`);
log.error(`alipay authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.realName;
let openId = user.userId;
doPassportCallback(req, res, next, {
openId: user._json.openid,
unionId: user._json.unionid || user.id,
nickname: user._json.nickname || user.displayName,
sourceType: 'wechat',
rawUser: user
});
doPassportCallback(req, res, {
openId: openId,
nickname: nickname,
sourceType: 'alipay'
}).catch(next);
})(req, res, next);
}
};
exports.wechat = wechat;
module.exports = {
common: common,
wechat: wechat,
local: local,
sina: sina,
qq: qq,
alipay: alipay
};
... ...
/**
* 注册控制器
*/
'use strict';
const _ = require('lodash');
const passportHelper = require('../models/passport-helper');
const regService = require('../models/reg-service');
const userService = require('../models/user-service');
const authHelper = require('../models/auth-helper');
const config = require('../../../config/common');
let helpers = global.yoho.helpers;
let cache = global.yoho.cache;
let cookie = global.yoho.cookie;
let index = (req, res, next) => {
// 设置注册有效时间30分钟, 防机器刷
req.session._REG_EXPIRE = Date.now() + 1800000;
let refer = req.query.refer;
refer && res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
regService.getRegData().then((result) => {
res.render('reg/index', {
title: '新用户注册',
passport: {
region: passportHelper.getCountry(),
location: '+86',
captchaUrl: helpers.urlFormat('/passport/images', {t: Date.now()}),
itemUrl: helpers.urlFormat('/help/', {category_id: 9}),
referUrl: refer,
loginUrl: helpers.urlFormat('/signin.html', {refer: refer}),
coverHref: result.url,
coverImg: result.img,
regBtnText: '立即注册'
}
});
}).catch(next);
};
let checkMobile = (req, res, next) => {
let data = {
code: 400
};
let mobile = +req.body.mobile;
let area = +req.body.area;
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号码格式不正确';
return res.json(data);
}
// 判断手机号是否检查超过指定次数
let key = 'checkmobilenum_' + passportHelper.makeAreaMobile(area, mobile);
cache.get(key).then((checkNum) => {
checkNum = +(checkNum || 0);
cache.set(key, ++checkNum).catch(next);
if (checkNum > 500) {
data.message = '检查次数太多';
return res.json(data);
}
// 判断用户是否存在
return userService.findByMobileAsync(area, mobile).then((user) => {
if (!_.isEmpty(user)) {
data.message = '手机号码已经存在';
return res.json(data);
}
data.code = 200;
return res.json(data);
});
}).catch(next);
};
let picCaptcha = (req, res, next) => {
let verifyCode = _.trim(req.body.verifyCode);
let picFlag = true; // TODO: 图形验证码校验
if (picFlag) {
return res.json({
code: 200,
message: '验证码正确'
});
}
return res.json({
code: 400,
message: '验证码错误'
});
};
let sendBindMsg = (req, res, next) => {
let data = {
code: 400,
message: '',
data: ''
};
let mobile = +req.body.mobile;
let area = +req.body.area;
let verifyCode = +req.body.verifyCode;
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号码格式不正确';
return res.json(data);
}
// 检查是否检查过
// let makeMobile = passportHelper.makeAreaMobile(area, mobile);
//
// if (req.session[`checkmobile_${makeMobile}`] !== makeMobile) {
// data.message = '发送失败';
// return res.json(data);
// }
// 校验是否发送过多
let sendCodeKey = `send_code_${area}_${mobile}`;
cache.get(sendCodeKey).then((sendCodeTimes) => {
if (!sendCodeTimes) {
sendCodeTimes = 0;
} else {
sendCodeTimes = +sendCodeTimes;
}
if (sendCodeTimes >= 10) {
data.message = '您已多次提交验证码,请尽快联系客服解决';
return res.json(data);
}
if (sendCodeTimes >= 5) {
data.message = '您收到的验证码短信已超过本日限定最多次数,请您耐心等待';
return res.json(data);
}
// TODO: 检测验证码不正确
// if (!PassportModel::verifyCode($verifyCode)) {
// $data['code'] = 400;
// $data['message'] = '图形验证码不正确';
// break;
// }
/* 向手机发送注册验证码 */
return regService.sendCodeToMobile(area, mobile).then((result) => {
return cache.set(sendCodeKey, sendCodeTimes + 1, 3600).then(() => {
if (result.code) {
return res.json(result);
} else {
data.message = '发送失败';
return res.json(data);
}
});
});
}).catch(next);
};
let msgCaptcha = (req, res, next) => {
let data = {
code: 400,
message: '',
data: ''
};
let area = +req.body.area;
let mobile = +req.body.mobile;
let code = +req.body.code; // 短信验证码
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号码格式不正确';
return res.json(data);
}
regService.validMobileCode(area, mobile, code).then((result) => {
if (result.code) {
return res.json(result);
} else {
data.message = '验证码错误';
return res.json(data);
}
}).catch(next);
};
let mobileRegister = (req, res, next) => {
let data = {
code: 400,
message: '',
data: ''
};
let area = +req.body.area;
let mobile = +req.body.mobile;
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号码格式不正确';
return res.json(data);
}
/* 判断是否是有效的注册方式,防注册机刷 */
let regExpireTime = req.session._REG_EXPIRE;
if (!regExpireTime || regExpireTime < Date.now()) {
data.message = '注册超时';
return res.json(data);
}
// TODO: 检测验证码不正确
// $verifyCode = strtolower(trim($this->post('verifyCode'))); //图形验证码
// if (!PassportModel::verifyCode($verifyCode)) {
// $data['message'] = '验证码不正确';
// break;
// }
/* 判断密码是否符合规则 */
let code = +req.body.code; // 短信验证码
let password = req.body.password;
if (!helpers.verifyPassword(password)) {
data.message = '密码不正确';
return res.json(data);
}
/* IP仅允许点击注册500次/时 */
let ip = req.ip;
let ipKey = 'ip_register_' + ip;
cache.get(ipKey).then((ipTimes) => {
if (!ipTimes) {
ipTimes = 0;
} else {
ipTimes = +ipTimes;
}
if (ipTimes >= 500) {
data.message = '由于你IP受限无法注册';
return res.json(data);
}
return cache.set(ipKey, ipTimes + 1, 3600).then(() => {
/* 验证注册的标识码是否有效 */
return regService.validMobileCode(area, mobile, code).then((result) => {
if (!result.code || result.code !== 200) {
data.message = '验证码错误';
return res.json(data);
}
let shoppingKey = cookie.getShoppingKey(req);
/* 手机注册: 调用注册接口,ip限制计数 */
return regService.regMobile(area, mobile, password, shoppingKey).then((regResult) => {
if (!regResult.code || regResult.code !== 200) {
data.message = '注册失败';
return res.json(data);
}
// 返回跳转到来源页面
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = '/?go=1';
}
if (/sign|login/.test(refer)) {
refer = '/?go=1';
}
return authHelper.syncUserSession(regResult.data.uid, req, res).then(() => {
return res.json({
code: 200,
message: '注册成功',
data: {
href: helpers.urlFormat('/passport/reg/success', {
next: refer,
goShoppingUrl: config.siteUrl
})
}
});
});
});
});
});
}).catch(next);
};
let success = (req, res, next) => {
let goUrl = req.query.next || config.siteUrl;
let goShoppingUrl = req.query.goShoppingUrl || config.siteUrl;
regService.getRegData().then((result) => {
res.render('reg/success', {
title: '注册成功',
passport: {
goUrl: goUrl,
goShoppong: goShoppingUrl,
coverHref: result.url,
coverImg: result.img
}
});
}).catch(next);
};
module.exports = {
index,
success,
checkMobile,
picCaptcha,
sendBindMsg,
msgCaptcha,
mobileRegister
};
... ...
... ... @@ -26,7 +26,7 @@ app.engine('.hbs', hbs({
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: require(`${global.library}/helpers`)
helpers: global.yoho.helpers
}));
... ...
'use strict';
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const api = new API();
const md5 = require('md5');
class Auth {
const cache = global.yoho.cache;
const sign = global.yoho.sign;
const api = global.yoho.API;
static signinByOpenID(nickname, openId, sourceType, shoppingKey) {
const Auth = {
signin(area, profile, password, shoppingKey) {
let param = {
method: 'app.passport.signin',
area: area,
profile: profile,
password: password
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.post('', param);
},
signinByOpenID(nickname, openId, sourceType, shoppingKey) {
let param = {
nickname: nickname,
openId: openId,
... ... @@ -19,10 +33,9 @@ class Auth {
param.shopping_key = shoppingKey;
}
return api.get('', sign.apiSign(param));
}
static signinByWechat(nickname, openId, unionId, sourceType, shoppingKey) {
return api.get('', param);
},
signinByWechat(nickname, openId, unionId, sourceType, shoppingKey) {
let param = {
nickname: nickname,
openId: openId,
... ... @@ -35,25 +48,23 @@ class Auth {
param.shopping_key = shoppingKey;
}
return api.get('', sign.apiSign(param));
}
static profile(uid) {
return api.get('', param);
},
profile(uid) {
let param = {
uid: uid,
method: 'app.passport.profile'
};
return api.get('', sign.apiSign(param));
}
static syncUserSession(uid, req, res) {
return api.get('', param);
},
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}`;
let uidCookie = `{data.profile_name}::${data.uid}::${data.vip_info.title}::${token}`;
req.session._TOKEN = token;
req.session._LOGIN_UID = uid;
... ... @@ -61,12 +72,24 @@ class Auth {
res.cookie('_UID', uidCookie, {
domain: 'yohobuy.com'
});
res.cookie('_TOKEN', token, {
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);
},
rememberAccount(accountInfo, req, res) {
let aWeek = (new Date()).getTime() / 1000 + 504000; // 504000-一周
let rememKey = md5(md5(accountInfo.account + accountInfo.password + accountInfo.area));
res.cookie('isRemember', true, aWeek);
res.cookie('remem', rememKey, aWeek);
if (!cache.get(rememKey)) {
cache.set(rememKey, accountInfo, aWeek);
}
}
}
};
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) => {
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)=> {
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();
};
/**
* 返回一个 md5加密后的字符串
* @param data json
*/
const makeToken = (data) => {
let saltData = Object.assign({}, data, {key: SALT});
let str = _packageObject(saltData);
return _encodeMD5(str);
};
/**
* 验证 token 是否一致
*/
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 Promise = require('bluebird');
const co = Promise.coroutine;
const _ = require('lodash');
const moment = require('moment');
const helpers = global.yoho.helpers;
const api = require('./back-api');
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)
};
});
};
/**
* 手机 token 合法性验证
*/
const authRequest = (data, token) => {
if (!backHelper.validateToken(data, token)) {
return {};
}
let existTime = moment.duration(60, 'minutes').asSeconds();
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;
/**
* 资源码找资源
*/
const getResourceAsync = resourceCode => {
return serviceAPI.get('/operations/api/v5/resource/get', {
content_code: resourceCode
});
};
module.exports = {
getResourceAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
'use strict';
const api = require('./index-api');
/**
* 获得资源
*/
const getResourceAsync = (resourceCode) => {
return api.getResourceAsync(resourceCode)
.then(result => {
if (result.code === 200) {
return result.data;
} else {
return {};
}
})
.catch(() => {
return {};
});
};
module.exports = {
getResourceAsync
};
... ...
/**
* passport.js 支付宝登录插件
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const util = require('util');
const _ = require('lodash');
const md5 = require('md5');
const passport = require('passport-strategy');
// 支付宝网关地址
const ALIPAY_URL = 'https://mapi.alipay.com/gateway.do';
const defaultOptions = {
service: 'alipay.auth.authorize',
_input_charset: 'utf-8',
sign_type: 'MD5',
target_service: 'user.auth.quick.login'
};
/**
* 将参数排序,拼接成 "参数=参数值" 的格式
*
* @param {Object} params
*/
function paramsToRaw(params) {
let keys = Object.keys(params);
keys = keys.sort();
let string = '';
keys.forEach((key) => {
string += '&' + key + '=' + params[key];
});
string = string.substr(1);
return string;
}
function AlipayStrategy(options, verify) {
if (typeof options === 'function') {
verify = options;
}
passport.Strategy.call(this);
this.name = 'alipay';
this._verify = verify;
}
util.inherits(AlipayStrategy, passport.Strategy);
AlipayStrategy.prototype.authenticate = function(req, options) {
if (req.query && req.query.is_success && req.query.sign && req.query.sign_type) {
let query = req.query;
let sign = query.sign;
let signType = query.sign_type;
delete query.sign_type;
delete query.sign;
let signString = paramsToRaw(query) + options.key;
if (signType === 'MD5' && sign !== md5(signString)) {
this.error('alipay callback sign check fail');
this.fail('alipay callback sign check fail');
return;
}
if (req.query.is_success === 'T') {
let user = {
userId: req.query.user_id,
realName: req.query.realName,
email: req.query.email
};
this.success(user, null);
} else {
this.error('alipay login fail');
this.fail(req.error_code);
}
} else {
let params = _.assign(defaultOptions, options);
let signType = params.sign_type;
delete params.sign_type;
delete params.sign;
let signString = paramsToRaw(params) + options.key;
if (signType === 'MD5') {
params.sign = md5(signString);
params.sign_type = 'MD5';
}
this.redirect(ALIPAY_URL + '?' + paramsToRaw(params));
}
};
exports = module.exports = AlipayStrategy;
exports.Strategy = AlipayStrategy;
... ...
/**
* Created by TaoHuang on 2016/6/20.
*/
'use strict';
const Promise = require('bluebird');
const co = Promise.coroutine;
const _ = require('lodash');
const helpers = global.yoho.helpers;
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: '中国香港'
}
];
};
/**
* 各国手机号规则
*/
const _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 verifyEmail = email => {
if (!email) {
return false;
}
const emailRegExp = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
return emailRegExp.test(email);
};
/**
* 验证手机是否合法
*/
const verifyMobile = phone => {
if (!phone) {
return false;
}
return /^1[3|4|5|8|7][0-9]{9}$/.test(phone);
};
/**
* 生成带区号的手机号码
*/
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: {
verifyMobile,
isAreaMobile,
verifyEmail,
isPassword
},
makeAreaMobile,
getCountry,
getLeftBannerAsync
};
... ...
/**
* 注册 model
*/
'use strict';
const passportHelper = require('./passport-helper');
const REGISTER_LEFT_BANNER_CODE = 'c479ec90120cae7f96e52922b4917064'; // 注册左边的banner
const api = global.yoho.API;
let getRegData = () => {
return passportHelper.getLeftBannerAsync(REGISTER_LEFT_BANNER_CODE);
};
let sendCodeToMobile = (area, mobile) => {
let params = {
method: 'app.register.sendRegCodeToMobile',
area: area,
mobile: mobile
};
return api.post('', params);
};
let validMobileCode = (area, mobile, code) => {
let params = {
method: 'app.register.validRegCode',
area: area,
mobile: mobile,
code: code
};
return api.post('', params);
};
let regMobile = (area, mobile, password, shoppingKey)=> {
let params = {
method: 'app.passport.register',
area: area,
profile: mobile,
password: password
};
if (shoppingKey) {
params.shopping_key = shoppingKey;
}
return api.post('', params);
};
module.exports = {
getRegData,
sendCodeToMobile,
validMobileCode,
regMobile
};
... ...
/**
* 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;
... ...
... ... @@ -10,9 +10,128 @@ const express = require('express');
const cRoot = './controllers';
const login = require(cRoot + '/login');
// const captcha = require(cRoot + '/captcha');
const back = require(cRoot + '/back');
const reg = require(cRoot + '/reg');
const router = express.Router(); // eslint-disable-line
router.get('/autosign/wechat', login.wechat.beforeLogin, login.wechat.login); // 微信登录, 兼容 PHP 的路径
// 本地登录
router.get('/login', login.common.beforeLogin, login.local.loginPage);
router.post('/login/auth', login.local.login);
router.get('/logout', login.local.logout);
// 微信登录
router.get('/autosign/wechat', login.common.beforeLogin, login.wechat.login); // 微信登录, 兼容 PHP 的路径
router.get('/login/wechat/callback', login.wechat.callback);
// sina登录
router.get('/autosign/sina', login.common.beforeLogin, login.sina.login);
router.get('/login/sina/callback', login.sina.callback);
// qq登录
router.get('/autosign/qq', login.common.beforeLogin, login.qq.login);
router.get('/login/qq/callback', login.qq.callback);
// alipay登录
router.get('/autosign/alipay', login.common.beforeLogin, login.alipay.login);
router.get('/login/alipay/callback', login.alipay.callback);
router.get('/login/account', login.common.needCaptcha);
/**
* 注册页面路由
*/
router.get('/reg/index', reg.index);
router.post('/reg/checkmobile', reg.checkMobile);
router.post('/reg/piccaptcha', reg.picCaptcha);
router.post('/reg/msgcaptcha', reg.msgCaptcha);
router.post('/reg/sendBindMsg', reg.sendBindMsg);
router.post('/reg/mobileregister', reg.mobileRegister);
router.get('/reg/success', reg.success);
/**
* 找回密码首页信息
*/
// 找回密码首页
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.validateSuccessStatusPage,
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="createdAt" value="{{createdAt}}">
<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="login-page passport-page yoho-page clearfix">
{{# passport}}
{{> login/cover}}
<div class="content">
<ul class="login-ul">
<li class="relative clearfix">
<h2 class="title">会员登录</h2>
<span id="country-code" class="country-code right">
<em>{{countryName}} {{countryCode}}</em>
<i class="iconfont">&#xe600;</i>
<ul id="country-list" class="country-list">
{{#each countryList}}
<li data-cc="{{areaCode}}" {{# selected}}selected{{/selected}}>{{name}} {{areaCode}}</li>
{{/each}}
</ul>
</span>
</li>
<li class="relative">
<input id="account" class="account input va" name="account" value="{{bindMobile}}" type="text" placeholder="邮箱/手机号码" autocomplete="off">
<span class="err-tip hide">
<i></i>
<em></em>
</span>
</li>
<li class="relative">
<input id="password" class="password input va" name="password" type="password" placeholder="密码" autocomplete="off" maxlength="20">
<span id="caps-lock" class="caps-lock hide">大写状态开启</span>
<span class="err-tip hide">
<i></i>
<em>请输入密码</em>
</span>
</li>
<li class="clearfix captcha-wrap hide">
<input id="captcha" class="input va captcha" type="text" name="captcha" placeholder="图形验证码" autocomplete="off" maxlength="4">
<img id="captcha-img" class="captcha-img" alt="">
<a class="link change-captcha">换一张</a>
<span class="err-tip hide">
<i></i>
<em></em>
</span>
</li>
<li>
<span id="login-btn" class="login-btn btn">登录</span>
</li>
<li class="other-opts">
<span class="remember-me">
<i class="iconfont">&#xe613;</i>
记住登录状态
</span>
<span class="right">
<a class="forget-password" href="{{forgetPwd}}">忘记密码?</a>
|
<a class="fast-reg" href="{{fastReg}}">快速注册</a>
</span>
</li>
<li class="third-party-login">
<a href="{{weixinLogin}}">
<span class="icon weixin"></span>
</a>
<a href="{{qqLogin}}">
<span class="icon qq"></span>
</a>
<a href="{{weiboLogin}}">
<span class="icon weibo"></span>
</a>
<a href="{{alipayLogin}}">
<span class="icon alipay"></span>
</a>
<a href="{{doubanLogin}}">
<span class="icon douban"></span>
</a>
<a href="{{renrenLogin}}">
<span class="icon renren"></span>
</a>
</li>
</ul>
<input id="country-code-hide" name="countryCode" type="hidden" value="{{countryCode}}">
</div>
{{/ passport}}
</div>
... ...
<div class="passport-page yoho-page clearfix">
{{# passport}}
{{> reg/cover}}
<div class="content">
{{> reg/register}}
</div>
{{/ passport}}
</div>
... ...
<div class="passport-page yoho-page clearfix">
{{# passport}}
{{> reg/cover}}
<div class="content">
<div class="register-page">
<div class="success-box">
<div class="success-text">
<span>恭喜!</span>账号注册成功,&nbsp;<span id="count-down">5</span>&nbsp;&nbsp;秒后将跳转至首页
</div>
<a class="success-btn" href="{{goShoppong}}" data-url="{{goUrl}}">随便逛逛</a>
</div>
</div>
</div>
{{/ passport}}
</div>
... ...
{{> layout/header}}
<div class="passport-page yoho-page clearfix">
{{# passport}}
{{> passport/cover}}
<div class="content">
{{> passport/register}}
</div>
{{/ passport}}
<div class="page-tip clearfix">为了给您更好的购物体验, 建议您创建YOHO!Family账号</div>
</div>
{{> layout/footer}}
\ No newline at end of file
... ...
<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>
... ...
<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>
\ No newline at end of file
... ...
<div class="register-page">
<ul>
<li class="clearfix">
<select id="region" class="region" name="region">
{{#each region}}
<option {{#if selected}}selected="selected"{{/if}} value="{{areaCode}}">{{name}}</option>
{{/each}}
</select>
</li>
<li class="clearfix" data-index="0">
<span id="country-code" class="country-code">{{location}}</span>
<input value="" id="phone-num" class="input va phone-num" type="text" name="phoneNum" placeholder="请输入手机号码" autocomplete="off">
</li>
<li class="w330 clearfix" data-index="1">
<input id="captcha" class="input va captcha" type="text" name="captcha" placeholder="图形验证码" autocomplete="off" maxlength="4">
<img id="captcha-img" class="captcha-img" src="{{captchaUrl}}" alt="">
<a class="link change-captcha">换一张</a>
</li>
<li class="clearfix" data-index="2">
<input id="msg-captcha" class="input va msg-captcha" type="text" name="msgCaptcha" placeholder="短信验证码" autocomplete="off" maxlength="4">
<input id="send-captcha" class="btn send-captcha disable" type="button" value="获取短信验证码">
<span id="msg-tip" class="hide msg-tip">短信验证码已发送至您的手机,请查收</span>
</li>
<li class="clearfix" data-index="3">
<input id="pwd" class="input va pwd" name="pwd" placeholder="设置密码" autocomplete="off" maxlength="20" type="password">
<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="hide pwd-tips">
<div class="default" id="pwd-tip1"><i></i>密码只支持6-20位字符</div>
<div class="default" id="pwd-tip2"><i></i>由字母、 数字组合,不能包含特殊符号</div>
</div>
</li>
<li class="items-container clearfix">
<input id="agree-terms" class="agree-terms" type="checkbox" checked="">
<span>
我已阅读并同意遵守
<a class="link go-yoho-items" href="{{itemUrl}}" target="_blank">YOHO!BUY 有货服务条款</a>
</span>
</li>
<li class="clearfix">
<input name="refer" id="refer" type="hidden" value="{{referUrl}}">
<input id="register-btn" class="btn register-btn disable" type="submit" value="{{regBtnText}}" disabled="">
</li>
{{# loginUrl}}
<li class="quick-login-container">
我已注册YOHO!BUY 有货账号
<a class="link go-login" href="{{.}}">快速登录</a>
</li>
{{/loginUrl}}
{{# skipUrl}}
<li class="skip-user-info">
<a href="{{.}}">跳过此步</a>
</li>
{{/skipUrl}}
</ul>
<div id="err-tip" class="err-tip hide">
<span></span>
<b></b>
</div>
<input name="" type="hidden" id="open-id" value="{{openId}}"/>
<input name="" type="hidden" id="source-type" value="{{sourceType}}"/>
</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>
\ No newline at end of file
... ...
<div class="register-page">
<ul>
<li class="clearfix">
<select id="region" class="region" name="region">
{{#each region}}
<option {{#if selected}}selected="selected"{{/if}} value="{{areaCode}}">{{name}}</option>
{{/each}}
</select>
</li>
<li class="clearfix" data-index="0">
<span id="country-code" class="country-code">{{location}}</span>
<input value="" id="phone-num" class="input va phone-num" type="text" name="phoneNum" placeholder="请输入手机号码" autocomplete="off">
</li>
<li class="w330 clearfix" data-index="1">
<input id="captcha" class="input va captcha" type="text" name="captcha" placeholder="图形验证码" autocomplete="off" maxlength="4">
<img id="captcha-img" class="captcha-img" src="{{captchaUrl}}" alt="">
<a class="link change-captcha">换一张</a>
</li>
<li class="clearfix" data-index="2">
<input id="msg-captcha" class="input va msg-captcha" type="text" name="msgCaptcha" placeholder="短信验证码" autocomplete="off" maxlength="4">
<input id="send-captcha" class="btn send-captcha disable" type="button" value="获取短信验证码">
<span id="msg-tip" class="hide msg-tip">短信验证码已发送至您的手机,请查收</span>
</li>
<li class="clearfix" data-index="3">
<input id="pwd" class="input va pwd" name="pwd" placeholder="设置密码" autocomplete="off" maxlength="20" type="password">
<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="hide pwd-tips">
<div class="default" id="pwd-tip1"><i></i>密码只支持6-20位字符</div>
<div class="default" id="pwd-tip2"><i></i>由字母、 数字组合,不能包含特殊符号</div>
</div>
</li>
<li class="items-container clearfix">
<input id="agree-terms" class="agree-terms" type="checkbox" checked="">
<span>
我已阅读并同意遵守
<a class="link go-yoho-items" href="{{itemUrl}}" target="_blank">YOHO!BUY 有货服务条款</a>
</span>
</li>
<li class="clearfix">
<input name="refer" id="refer" type="hidden" value="{{referUrl}}">
<input id="register-btn" class="btn register-btn disable" type="submit" value="{{regBtnText}}" disabled="">
</li>
{{# loginUrl}}
<li class="quick-login-container">
我已注册YOHO!BUY 有货账号
<a class="link go-login" href="{{.}}">快速登录</a>
</li>
{{/loginUrl}}
{{# skipUrl}}
<li class="skip-user-info">
<a href="{{.}}">跳过此步</a>
</li>
{{/skipUrl}}
</ul>
<div id="err-tip" class="err-tip hide">
<span></span>
<b></b>
</div>
<input name="" type="hidden" id="open-id" value="{{openId}}"/>
<input name="" type="hidden" id="source-type" value="{{sourceType}}"/>
</div>
... ...