Authored by biao

Merge branch 'master' of http://git.yoho.cn/fe/yohobuywap-node

Showing 66 changed files with 3088 additions and 516 deletions

Too many changes to show.

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

... ... @@ -132,7 +132,7 @@ Session.vim
*~
# auto-generated tag files
tags
### VS Code ###
.vscode/
... ... @@ -143,3 +143,4 @@ public/bundle/*
.eslintcache
*.log.*
nbproject/*
.DS_Store
... ...
{
"extends": "stylelint-config-yoho"
"extends": "stylelint-config-yoho",
"rules": {
"string-quotes": "double"
}
}
... ...
... ... @@ -17,6 +17,7 @@ const path = require('path');
const bodyParser = require('body-parser');
const cookieParser = require('cookie-parser');
const favicon = require('serve-favicon');
const yohoLib = require('yoho-node-lib');
const session = require('express-session');
const memcached = require('connect-memcached');
const hbs = require('express-handlebars');
... ... @@ -29,11 +30,14 @@ const MemcachedStore = memcached(session);
app.locals.devEnv = app.get('env') === 'development';
app.locals.version = pkg.version;
// 全局注册library
yohoLib.global(config);
// 指定libray目录
global.library = path.resolve('./library/');
global.middleware = path.resolve('./doraemon/middleware');
global.utils = path.resolve('./utils');
const logger = global.yoho.logger;
app.set('view engine', '.hbs');
app.set('views', './doraemon/views');
app.engine('.hbs', hbs({
... ... @@ -41,7 +45,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')));
... ... @@ -76,17 +80,18 @@ app.use((req, res, next) => {
next();
});
const logger = require('./library/logger');
// dispatcher
try {
const user = require('./doraemon/middleware/user');
const setChannel = require('./doraemon/middleware/set-channel');
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(setYohoData());
app.use(user());
app.use(setChannel());
app.use(setPageInfo());
require('./dispatch')(app);
... ... @@ -95,6 +100,7 @@ try {
// YOHO 后置中间件
app.use(errorHanlder.serverError());
} catch (err) {
console.error(err);
logger.error(err);
}
... ...
... ... @@ -2,6 +2,7 @@
## 构建方法
* `npm i -g yo`
* `npm i -g generator-subapp`
* `cd apps`
* `yo subapp`
... ...
'use strict';
const mRoot = '../models';
const library = '../../../library';
const couponModel = require(`${mRoot}/coupon`); // 领取优惠券 model
const log = require(`${library}/logger`);
exports.index = (req, res) => {
exports.index = (req, res, next) => {
var renderData = {
module: 'coupon',
page: 'index',
module: 'activity',
page: 'coupon',
appDownLoadLink: 'http://a.app.qq.com/o/simple.jsp?pkgname=com.yoho'
},
ordercode = req.query.ordercode,
... ... @@ -33,19 +31,18 @@ exports.index = (req, res) => {
couponData.appDownLoadLink = renderData.appDownLoadLink;
res.render('coupon', {
result: couponData,
module: 'coupon',
page: 'index',
module: 'activity',
page: 'coupon',
wechatShare: true,
title: couponData.title
});
}).catch((err) => {
log.error('频道页面渲染错误:' + JSON.stringify(err));
});
}).catch(next);
};
exports.getCoupon = (req, res) => {
exports.getCoupon = (req, res, next) => {
var renderData = {
module: 'coupon',
page: 'index'
module: 'activity',
page: 'coupon'
};
var ordercode = req.query.ordercode,
... ... @@ -68,15 +65,13 @@ exports.getCoupon = (req, res) => {
res.json({
result: couponData
});
}).catch((err) => {
log.error('频道页面渲染错误:' + JSON.stringify(err));
});
}).catch(next);
};
exports.verify = (req, res) => {
exports.verify = (req, res, next) => {
var renderData = {
module: 'coupon',
page: 'index'
module: 'activity',
page: 'coupon'
};
... ... @@ -103,7 +98,5 @@ exports.verify = (req, res) => {
res.json({
result: couponData
});
}).catch((err) => {
log.error('频道页面渲染错误:' + JSON.stringify(err));
});
}).catch(next);
};
... ...
/**
* 微信分享签名
* Bi Kai <kai.bi@yoho.cn>
*/
'use strict';
const wechatModel = require('../models/wechat');
... ...
... ... @@ -23,7 +23,7 @@ app.engine('.hbs', hbs({
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: ['./views/partial', `${doraemon}/partial`],
helpers: require(`${global.library}/helpers`)
helpers: global.yoho.helpers
}));
// router
... ...
var API = require('../../../library/api').API;
var api = new API();
const library = '../../../library';
const sign = require(`${library}/sign`);
var api = global.yoho.API;
/**
* 分享页面基础参数
... ... @@ -10,30 +7,32 @@ const sign = require(`${library}/sign`);
*/
const getPageInfo = (pageInfo) => {
var dest = {};
dest.shareTitle = pageInfo.data.shareTitle;
dest.shareDesc = pageInfo.data.shareContent;
dest.shareImg = pageInfo.data.shareImgUrl;
dest.shareLink = pageInfo.data.shareUrl;
dest.code = pageInfo.code;
dest.activityID = pageInfo.id;
dest.title = pageInfo.data.h5Title;
dest.activityDesc = pageInfo.data.activityDesc;
dest.couponPic = pageInfo.data.couponPic;
dest.oldUserCouponPic = pageInfo.data.oldUserCouponPic;
dest.mobile = pageInfo.data.mobile;
// 强制活动开始,活动上线产品要求这样设置
pageInfo.data.flag = 1;
if (pageInfo.data.flag === 1) {
dest.bgImg = pageInfo.data.activityNormalPic;
} else {
dest.bgImg = pageInfo.data.activityEndPic;
dest.ended = true;
if (pageInfo && pageInfo.data) {
dest.shareTitle = pageInfo.data.shareTitle;
dest.shareDesc = pageInfo.data.shareContent;
dest.shareImg = pageInfo.data.shareImgUrl;
dest.shareLink = pageInfo.data.shareUrl;
dest.code = pageInfo.code;
dest.activityID = pageInfo.id;
dest.title = pageInfo.data.h5Title;
dest.activityDesc = pageInfo.data.activityDesc;
dest.couponPic = pageInfo.data.couponPic;
dest.oldUserCouponPic = pageInfo.data.oldUserCouponPic;
dest.mobile = pageInfo.data.mobile;
dest.wechatShare = true;
// 强制活动开始,活动上线产品要求这样设置
pageInfo.data.flag = 1;
if (pageInfo.data.flag === 1) {
dest.bgImg = pageInfo.data.activityNormalPic;
} else {
dest.bgImg = pageInfo.data.activityEndPic;
dest.ended = true;
}
dest.message = pageInfo.data.returnMsg;
}
dest.message = pageInfo.data.returnMsg;
// 清空变量,释放内存
pageInfo = {};
return dest;
};
... ... @@ -41,7 +40,6 @@ const getUserStatus = (param) => {
var dest = {};
dest.code = param.code;
dest.title = param.data.h5Title;
dest.returnCode = param.data.returnCode;
dest.mobile = param.data.mobile;
if (param.data.returnCode === 0) {
... ... @@ -73,13 +71,11 @@ const getUserStatus = (param) => {
*/
exports.getPageInfo = (data) => {
var defaultParam = {
method: 'app.activity.getInfoOfOrderShare'
},
infoData = Object.assign(defaultParam, data); // 处理完成后,发给后端
// 处理完成后,发给后端
infoData = Object.assign(defaultParam, data);
return api.get('', sign.apiSign(infoData)).then(result => {
return api.get('', infoData).then(result => {
return getPageInfo(result);
}); // 所有数据返回一个 Promise,方便 Promise.all 调用
};
... ... @@ -95,7 +91,7 @@ exports.getCoupon = (data) => {
// 处理完成后,发给后端
phoneData = Object.assign(defaultParam, data);
return api.get('', sign.apiSign(phoneData)).then(result => {
return api.get('', phoneData).then(result => {
return getUserStatus(result);
}); // 所有数据返回一个 Promise,方便 Promise.all 调用
};
... ... @@ -112,7 +108,7 @@ exports.registerAndSendCoupon = (data) => {
// 处理完成后,发给后端
verifyData = Object.assign(defaultParam, data);
return api.get('', sign.apiSign(verifyData)).then(result => {
return api.get('', verifyData).then(result => {
return getUserStatus(result);
}); // 所有数据返回一个 Promise,方便 Promise.all 调用
};
... ...
... ... @@ -8,8 +8,8 @@
const request = require('request-promise');
const Promise = require('bluebird');
const crypto = require('crypto');
const logger = require('../../../library/logger');
const cache = require('../../../library/cache');
const logger = global.yoho.logger;
const cache = global.yoho.cache;
// 此处请勿使用有货公众号的 appId, 此处使用的是 女生志 的appId
const appId = 'wxb52ec6a352f0b090';
... ... @@ -27,80 +27,80 @@ const ticketCacheKey = 'wechatShare:ticket';
// 微信 JS 接口签名校验工具 http://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=jsapisign
const wechat = {
getAccessToken: Promise.coroutine(function* () {
let accessToken = yield cache.get(accessTokenCacheKey);
if (accessToken) {
return accessToken;
}
logger.info('调用微信 API 获取 accessToken');
return request({
url: 'https://api.weixin.qq.com/cgi-bin/token',
qs: {
grant_type: 'client_credential',
appid: appId,
secret: secret
},
json: true
}).then((res) => {
// accessToken 有效期 7200s,缓存 7100s
cache.set(accessTokenCacheKey, res.access_token, 7100).catch((err) => {
logger.error('微信分享 Token, 缓存 accessToken 时出错', JSON.stringify(err));
});
return res.access_token;
}).catch((err) => {
logger.error('微信分享 Token, 获取 accessToken 时出错', JSON.stringify(err));
let _getAccessToken = Promise.coroutine(function* () {
let accessToken = yield cache.get(accessTokenCacheKey);
if (accessToken) {
return accessToken;
}
logger.info('get accessToken from wechat API');
return request({
url: 'https://api.weixin.qq.com/cgi-bin/token',
qs: {
grant_type: 'client_credential',
appid: appId,
secret: secret
},
json: true
}).then((res) => {
// accessToken 有效期 7200s,缓存 7100s
cache.set(accessTokenCacheKey, res.access_token, 7100).catch((err) => {
logger.error('set wechat accessToken cache error', JSON.stringify(err));
});
}),
getTicket: Promise.coroutine(function* () {
let ticket = yield cache.get(ticketCacheKey);
if (ticket) {
return ticket;
}
logger.info('调用微信 API 获取 ticket');
return request({
url: 'https://api.weixin.qq.com/cgi-bin/ticket/getticket',
qs: {
access_token: yield this.getAccessToken(),
type: 'jsapi'
},
json: true
}).then(res => {
return res.access_token;
}).catch((err) => {
logger.error('get accessToken from wechat API error', JSON.stringify(err));
});
});
let _getTicket = Promise.coroutine(function* () {
let ticket = yield cache.get(ticketCacheKey);
if (ticket) {
return ticket;
}
logger.info('get ticket from wechat API');
return request({
url: 'https://api.weixin.qq.com/cgi-bin/ticket/getticket',
qs: {
access_token: yield _getAccessToken(),
type: 'jsapi'
},
json: true
}).then(res => {
// ticket 有效期 7200s,缓存 7100s
cache.set(ticketCacheKey, res.ticket, 7100).catch((err) => {
logger.error('微信分享 Token, 缓存 ticket 时出错', JSON.stringify(err));
});
return res.ticket;
}).catch((err) => {
logger.error('微信分享 Token, 获取 ticket 时出错', JSON.stringify(err));
cache.set(ticketCacheKey, res.ticket, 7100).catch((err) => {
logger.error('set wechat Token cache error', JSON.stringify(err));
});
}),
calcSignature: Promise.coroutine(function* (data) {
data = Object.assign({
nonceStr: Math.random().toString(36).substr(2, 15),
timestamp: Math.floor(Date.now() / 1000) + '',
ticket: yield this.getTicket(),
appId: appId
}, data);
const str = `jsapi_ticket=${data.ticket}&noncestr=${data.nonceStr}&timestamp=${data.timestamp}&url=${data.url}`;
data.signature = sha1(str);
return data;
})
};
return res.ticket;
}).catch((err) => {
logger.error('get ticket from wechat API error', JSON.stringify(err));
});
});
let calcSignature = Promise.coroutine(function* (data) {
data = Object.assign({
nonceStr: Math.random().toString(36).substr(2, 15),
timestamp: Math.floor(Date.now() / 1000) + '',
ticket: yield _getTicket(),
appId: appId
}, data);
const str = `jsapi_ticket=${data.ticket}&noncestr=${data.nonceStr}&timestamp=${data.timestamp}&url=${data.url}`;
data.signature = sha1(str);
return data;
});
// 测试
// wechat.calcSignature({
// calcSignature({
// url: 'http://www.yohobuy.com/'
// }).then(console.log);
module.exports = wechat;
module.exports = {
calcSignature
};
... ...
... ... @@ -4,8 +4,8 @@
<img src="{{image bgImg 640 1136}}">
</div>
<p class="hidden" id="orderCode">{{ordercode}}</p>
<p class="hidden" id="oldUserCouponPic">{{oldUserCouponPic}}</p>
<p class="hidden" id="newUserCouponPic">{{couponPic}}</p>
<p class="hidden" id="oldUserCouponPic">{{image oldUserCouponPic}}</p>
<p class="hidden" id="newUserCouponPic">{{image couponPic}}</p>
<p class="hidden" id="activityEnded">{{ended}}</p>
<p class="hidden" id="newUser">{{newUser}}</p>
<p class="hidden" id="tipMessage">{{message}}</p>
... ... @@ -18,7 +18,7 @@
<div class="page">
<div class="gain-coupon-centent hidden">
<div class="coupon">
<img src={{couponPic}}>
<img src="{{image couponPic}}">
</div>
<p class="phone" id="mobile"></p>
<p>登录Yoho!Buy有货客户端即可使用</p>
... ... @@ -44,7 +44,7 @@
<div class="input-content vertify-input-content hidden">
<input id="verification" type="text" placeholder="请输入验证码" maxlength="4"/>
<i class="clear-input iconfont hidden clear-verify">&#xe626;</i>
<div>验证领红包</div>
<div>注册领红包</div>
</div>
<div class="coupon-description">
... ...
... ... @@ -4,12 +4,12 @@
* @date: 2016/05/09
*/
'use strict';
const library = '../../../library';
const _ = require('lodash');
const channelModel = require('../models/channel');
const helpers = require(`${library}/helpers`);
const renderData = {
const helpers = global.yoho.helpers;
let _renderData = {
module: 'channel',
page: 'home',
homeHeader: {
... ... @@ -33,19 +33,19 @@ const renderData = {
* @param {[object]} data 自定义数据
* @return {[type]}
*/
const channelPage = (req, res, data) => {
return channelModel.getChannelData({ // TODO 内部的Promise方法必须 return 出来
let _channelPage = (req, res, data) => {
return channelModel.getChannelData({
gender: data.gender,
uid: _.toString(req.user.uid)
}).then(result => {
res.render('channel', Object.assign({}, renderData, data, result));
res.render('channel', Object.assign({}, _renderData, data, result));
});
};
/**
* 频道选择页
*/
exports.index = (req, res, next) => {
let index = (req, res, next) => {
channelModel.getChannelSwitchData().then((result) => {
res.render('index', {
module: 'channel',
... ... @@ -53,7 +53,8 @@ exports.index = (req, res, next) => {
title: 'Yoho!Buy 有货',
searchUrl: helpers.urlFormat('/', null, 'search'),
pageFooter: true,
channelList: result[0],
channelList: result[0].channelList,
yohood: result[0].yohood,
background: result[1]
});
}).catch(next);
... ... @@ -66,24 +67,24 @@ exports.index = (req, res, next) => {
* @param {Function} next
* @return {Function}
*/
exports.switchChannel = (req, res, next) => {
let channel = req.cookies._Channel;
let switchChannel = (req, res, next) => {
let channel = req.yoho.channel;
// 如果查询字符串设置了 go 参数,跳转到 cookie 中设置的频道页
if (req.query.go && channel) {
res.redirect('/' + channel);
} else {
// 设置浏览器缓存5分钟 300000ms
res.set('Expires', (new Date(_.now() + 300000)).toGMTString());
return next();
return res.redirect('/' + channel);
}
// 设置浏览器缓存5分钟 300000ms
res.set('Expires', (new Date(_.now() + 300000)).toGMTString());
next();
};
/**
* 男生首页
*/
exports.boys = (req, res, next) => {
channelPage(req, res, {
let boys = (req, res, next) => {
_channelPage(req, res, {
gender: 'boys',
title: '男生首页',
boysHomePage: true
... ... @@ -93,8 +94,8 @@ exports.boys = (req, res, next) => {
/**
* 女生首页
*/
exports.girls = (req, res, next) => {
channelPage(req, res, {
let girls = (req, res, next) => {
_channelPage(req, res, {
gender: 'girls',
title: '女生首页',
girlsHomePage: true
... ... @@ -105,8 +106,8 @@ exports.girls = (req, res, next) => {
* 潮童首页
*/
exports.kids = (req, res, next) => {
channelPage(req, res, {
let kids = (req, res, next) => {
_channelPage(req, res, {
gender: 'kids',
title: '潮童首页',
kidsHomePage: true
... ... @@ -116,8 +117,8 @@ exports.kids = (req, res, next) => {
/**
* 创意生活首页
*/
exports.lifestyle = (req, res, next) => {
channelPage(req, res, {
let lifestyle = (req, res, next) => {
_channelPage(req, res, {
gender: 'lifestyle',
title: '创意生活首页',
lifestyleHomePage: true
... ... @@ -130,10 +131,20 @@ exports.lifestyle = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.bottomBanner = (req, res, next) => {
let bottomBanner = (req, res, next) => {
let gender = req.query.gender || 'boys';
channelModel.getBottomBannerData(gender).then(result => {
res.send(result);
}).catch(next);
};
module.exports = {
switchChannel,
index,
boys,
girls,
kids,
lifestyle,
bottomBanner
};
... ...
... ... @@ -24,7 +24,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
}));
// router
... ...
... ... @@ -4,16 +4,14 @@
* @date: 2016/05/09
*/
'use strict';
const library = '../../../library';
const utils = '../../../utils';
const contentCodeConfig = require('../../../config/content-code');
const _ = require('lodash');
const ServiceAPI = require(`${library}/api`).ServiceAPI;
const sign = require(`${library}/sign`);
const camelCase = require(`${library}/camel-case`);
const logger = require(`${library}/logger`);
const api = global.yoho.ServiceAPI;
const camelCase = global.yoho.camelCase;
const logger = global.yoho.logger;
const resourcesProcess = require(`${utils}/resources-process`);
const api = new ServiceAPI();
/**
* 性别数据
... ... @@ -28,56 +26,44 @@ const genderData = {
* 楼层资源的位置码
* @type {Object}
*/
const contentCode = {
boys: '8512bf0755cc549ac323f852c9fd945d',
girls: '189b6686065dbd6755dd6906cf03c002',
kids: 'b8c1bff53d4ea60f978926d538620636',
lifestyle: '61cd852c6afcf60660196154f66a3a62',
index: '7ba9118028f9b22090b57341487567eb'
};
const contentCode = contentCodeConfig.channel;
/**
* 频道底部 Banner 位置码
* @type {Object}
*/
const bottomBannerCode = {
boys: 'a2ec977c027d0cd9cdccb356ddf16b08',
girls: '8c8bd1b89a22e5895f05882e0825b493'
};
const bottomBannerCode = contentCodeConfig.bottom;
/**
* 频道选择页 默认数据
* @type {Object}
*/
const channelList = {
1: {
const channelList = [
{
href: '/boys',
title: '男生',
entitle: 'BOYS'
},
2: {
}, {
href: '/girls',
title: '女生',
entitle: 'GIRLS'
},
3: {
}, {
href: '/kids',
title: '潮童',
entitle: 'KIDS'
},
4: {
}, {
href: '/lifestyle',
title: '创意生活',
entitle: 'LIFESTYLE'
}
};
];
/**
* 获取二级菜单顶部颜色
* @param {[string]} choosed
* @return {[string]}
*/
const getSidebarColor = (choosed) => {
const _getSidebarColor = (choosed) => {
let color = false;
if (choosed === 'girls') {
... ... @@ -96,7 +82,7 @@ const getSidebarColor = (choosed) => {
* @param {[array]} list
* @return {[array]}
*/
const processSideBar = (list, choosed) => {
const _processSideBar = (list, choosed) => {
const formatData = [];
let offset = 0; // 分割数组用到的游标
... ... @@ -110,7 +96,7 @@ const processSideBar = (list, choosed) => {
sortNameEn: item.sortNameEn,
back: true,
isSelect: false,
bgColor: getSidebarColor(choosed)
bgColor: _getSidebarColor(choosed)
});
}
... ... @@ -131,7 +117,7 @@ const processSideBar = (list, choosed) => {
* @param {[object]} gender
* @return {[type]}
*/
const getChannelResource = (params) => {
const _getChannelResource = (params) => {
params.gender = params.gender || 'boys';
params = Object.assign({
... ... @@ -144,7 +130,9 @@ const getChannelResource = (params) => {
params.new_device = true; // eslint-disable-line
}
return api.get('operations/api/v5/resource/home', sign.apiSign(params), true).then(result => {
return api.get('operations/api/v5/resource/home', params, {
cache: true
}).then(result => {
if (result && result.code === 200) {
return resourcesProcess(result.data.list);
} else {
... ... @@ -159,12 +147,14 @@ const getChannelResource = (params) => {
* @param {[string]} choosed
* @return {[object]}
*/
const getLeftNav = (choosed) => {
const _getLeftNav = (choosed) => {
choosed = choosed || 'all';
return api.get('operations/api/v6/category/getCategory', sign.apiSign({}), true).then(result => {
return api.get('operations/api/v6/category/getCategory', {}, {
cache: true
}).then(result => {
if (result && result.code === 200) {
return processSideBar(result.data, choosed);
return _processSideBar(result.data, choosed);
} else {
logger.error('侧边栏数据接口返回状态码 不是 200');
return result;
... ... @@ -176,23 +166,27 @@ const getLeftNav = (choosed) => {
* 获取频道选择页数据
* @return {[type]}
*/
const getChannelList = () => {
return api.get('operations/api/v5/entrance/getEntrance', sign.apiSign({}), true).then((result) => {
const _getChannelList = () => {
return api.get('operations/api/v5/entrance/getEntrance', {}, {
cache: true
}).then((result) => {
if (result && result.code === 200) {
const list = {};
list.channelList = [];
list.yohood = {};
result.data.list = camelCase(result.data.list || []);
_.forEach(result.data.list, function(item) {
const channel = channelList[item.channelId];
const channel = channelList[item.yhChannel - 1];
if (channel) {
list[item.channelId] = channelList[item.channelId];
list.channelList.push(channel);
}
if (_.toNumber(item.channelId) === 5) {
list.showYohood = true;
list.yohoodHref = 'http://yohood.cn';
if (_.toNumber(item.yhChannel) === 5) {
list.yohood.showYohood = true;
list.yohood.yohoodHref = 'http://www.yohood.cn';
}
});
return Object.keys(list).length ? list : channelList;
... ... @@ -207,10 +201,12 @@ const getChannelList = () => {
* 获取频道选择页 背景
* @return {[type]}
*/
const getChannelBg = () => {
return api.get('operations/api/v5/resource/get', sign.apiSign({
const _getChannelBg = () => {
return api.get('operations/api/v5/resource/get', {
content_code: contentCode.index
}), true).then(result => {
}, {
cache: true
}).then(result => {
if (result && result.code === 200) {
return result.data.length && result.data[0] && result.data[0].data && result.data[0].data.list[0];
} else {
... ... @@ -227,8 +223,8 @@ const getChannelBg = () => {
* @param {[string]} gender
* @return {[type]}
*/
exports.getChannelSwitchData = () => {
return Promise.all([getChannelList(), getChannelBg()]);
let getChannelSwitchData = () => {
return Promise.all([_getChannelList(), _getChannelBg()]);
};
/**
... ... @@ -236,10 +232,10 @@ exports.getChannelSwitchData = () => {
* @param {[object]} params
* @return {[object]}
*/
exports.getChannelData = (params) => {
let getChannelData = (params) => {
var channelData = {};
return Promise.all([getChannelResource(params), getLeftNav(params.gender)]).then((data) => {
return Promise.all([_getChannelResource(params), _getLeftNav(params.gender)]).then((data) => {
channelData.content = data[0]; // 资源位数据
channelData.sideNav = data[1]; // 侧边栏数据
... ... @@ -252,13 +248,13 @@ exports.getChannelData = (params) => {
* @param {[string]} gender
* @return {[type]}
*/
exports.getBottomBannerData = (gender) => {
let getBottomBannerData = (gender) => {
gender = gender || 'boys';
if (gender === 'boys' || gender === 'girls') {
return api.get('operations/api/v5/resource/get', sign.apiSign({
return api.get('operations/api/v5/resource/get', {
content_code: bottomBannerCode[gender] // eslint-disable-line
}), true);
}, true);
}
return Promise.resolve({
code: 400,
... ... @@ -266,3 +262,9 @@ exports.getBottomBannerData = (gender) => {
message: '参数错误'
});
};
module.exports = {
getChannelData,
getChannelSwitchData,
getBottomBannerData
};
... ...
<div class="mobile-container">
<div class="mobile-wrap {{#if boysHomePage}}boys-wrap{{/if}} {{#if girlsHomePage}}girls-wrap{{/if}} {{#if kidsHomePage}}kids-wrap{{/if}} {{#if lifestyleHomePage}}lifestyle-wrap{{/if}} yoho-page">
<div class="mobile-wrap {{#if boysHomePage}}boys-wrap{{/if}} {{#if girlsHomePage}}girls-wrap{{/if}} {{#if kidsHomePage}}kids-wrap{{/if}} {{#if lifestyleHomePage}}lifestyle-wrap{{/if}} yoho-page" data-channel="{{gender}}">
{{! 首页header}}
{{#homeHeader}}
{{> channel/home-header}}
... ...
... ... @@ -24,9 +24,11 @@
<span class="iconfont right-icon">&#xe614;</span>
</a>
{{/each}}
{{#showYohood}}
<a href="{{yohoodHref}}" id="yohood" class="list-item"> <span class="iconfont right-icon">&#xe614;</span></a>
{{/showYohood}}
{{#yohood}}
{{#if showYohood}}
<a href="{{yohoodHref}}" id="yohood" class="list-item"> <span class="iconfont right-icon">&#xe614;</span></a>
{{/if}}
{{/yohood}}
</div>
</div>
</div>
... ...
{{# content}}
{{! 头部banner}}
{{#if focus}}
{{> resources/banner-top}}
{{/if}}
{{! 图标入口}}
{{#if appIconList}}
{{> resources/icons-enter}}
{{/if}}
{{! 中间banner}}
{{#if singleImage}}
{{> resources/banner}}
{{/if}}
{{! 两个小图}}
{{#if smallPic}}
{{> resources/thumb-row}}
{{/if}}
{{! 热门品类}}
{{#if recommendContentFive}}
{{> resources/hot-category}}
{{/if}}
{{! 热门品牌}}
{{#if appHotBrands}}
{{> resources/hot-brands-list}}
{{/if}}
{{! 热门品牌可滑动}}
{{#if customBrands}}
{{> resources/hot-brands-swipe}}
{{/if}}
{{! 潮人搭配}}
{{#if trendsetterCollocation}}
{{> resources/trendsetter-collocation}}
{{/if}}
{{! 潮流话题}}
{{#if trendgoodsTopic}}
{{> resources/trend-topics}}
{{/if}}
{{! 潮流时装/经典裤裙/时尚靴履/潮人配饰/潮流上装}}
{{#if recommendContentOne}}
{{> resources/goods-category}}
{{/if}}
{{! 新入住品牌/品味生活/创意生活}}
{{#if recommendContentTwo}}
{{> resources/creative-life}}
{{/if}}
{{! PLUS}}
{{#if singleNameImage}}
{{> resources/plus-star}}
{{/if}}
{{! 新人专享}}
{{#if newUserFloor}}
{{> resources/fresh-only}}
{{/if}}
{{/ content}}
... ... @@ -6,12 +6,8 @@
'use strict';
const mRoot = '../models';
const library = '../../../library';
const _ = require('lodash');
const starModel = require(`${mRoot}/star`);
const helpers = require(`${library}/helpers`);
// const headerModel = require('../../../doraemon/models/header');
const headTab = [
{
... ... @@ -28,17 +24,12 @@ const headTab = [
}
];
const processPublicData = (req, title) => {
// let headerData = headerModel.setNav({
// navTitle: title,
// navBtn: navBtn
// });
let md5Str = '';
const _processPublicData = (req, title) => {
let renderData = {
module: 'guang',
title: title
// pageHeader: headerData
};
if (req.query.app_version || req.query.appVersion) {
... ... @@ -54,28 +45,45 @@ const processPublicData = (req, title) => {
* @param {[object]} res
* @return {[type]}
*/
exports.index = (req, res) => {
let index = (req, res, next) => {
const pageHeadTab = _.cloneDeep(headTab);
pageHeadTab[0].cur = true;
res.render('star/index', _.assign({
page: 'star',
isStarIndexPage: true,
headTab: pageHeadTab
}, processPublicData(req, '星潮教室')));
};
exports.getIndexHtml = (req, res, next) => {
starModel.getIndexData().then((result) => {
res.render('star/index-html', _.assign({
layout: false,
md5Str = result.md5;
res.render('star/index', _.assign({
page: 'star',
isStarIndexPage: true,
headTab: pageHeadTab,
content: {
focus: true,
data: result.ads
},
starAvatar: result.starAvatar.concat(result.starAvatar).concat(result.starAvatar), // 需要优化,数组重复三次
articles: result.articles
}));
starAvatar: result.starAvatar ? result.starAvatar : '',
articles: result.articles,
md5: result.md5
}, _processPublicData(req, '星潮教室')));
}).catch(next);
};
let getIndexHtml = (req, res, next) => {
starModel.getIndexData().then((result) => {
if (md5Str !== result.md5) {
md5Str = result.md5;
res.render('star/index-html', _.assign({
layout: false,
content: {
focus: true,
data: result.ads
},
starAvatar: result.starAvatar ? result.starAvatar : '',
articles: result.articles
}));
} else {
res.json();
}
}).catch(next);
};
... ... @@ -85,16 +93,17 @@ exports.getIndexHtml = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.special = (req, res, next) => {
let special = (req, res, next) => {
starModel.getSpecialData().then((result) => {
const pageHeadTab = _.cloneDeep(headTab);
pageHeadTab[1].cur = true;
res.render('star/special', _.assign({
page: 'special'
}, processPublicData(req, '星潮教室'), {
}, _processPublicData(req, '星潮教室'), {
resources: result,
headTab: pageHeadTab
headTab: pageHeadTab,
notIndex: true
}));
}).catch(next);
};
... ... @@ -106,15 +115,16 @@ exports.special = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.collocation = (req, res) => {
let collocation = (req, res) => {
const pageHeadTab = _.cloneDeep(headTab);
pageHeadTab[2].cur = true;
res.render('star/collocation', _.assign({
isStarDetailPage: true,
page: 'collocation'
}, processPublicData(req, '星潮教室'), {
headTab: pageHeadTab
}, _processPublicData(req, '星潮教室'), {
headTab: pageHeadTab,
notIndex: true
}));
};
... ... @@ -124,9 +134,9 @@ exports.collocation = (req, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.collocationList = (req, res, next) => {
let collocationList = (req, res, next) => {
let params = req.query;
let uid = req.user.uid || 0;
let uid = req.cookies.guangStarUid || 0; // 客户端访问,不能使用 cookie
starModel.getCollocationListData(params, uid).then((result) => {
res.render('star/list', _.assign({
... ... @@ -144,22 +154,25 @@ exports.collocationList = (req, res, next) => {
* @param {[type]} res [description]
* @return {[type]}
*/
exports.setFavorite = (req, res, next) => {
let params = req.body;
let uid = req.user.uid || 0;
let isApp = req.query.app_version || req.query.appVersion || false;
let url = '/guang/star/collocation';
let urlEncode = '';
let setFavorite = (req, res, next) => {
let uid = req.cookies.guangStarUid || 0;
let params = {
articleId: req.body.articleId,
type: req.body.type
};
starModel.setFavorite(params, uid).then((result) => {
if (result.code === 401) {
if (!isApp) {
result.data = helpers.urlFormat('/signin.html', {
refer: req.get('Referer') || url
});
} else {
urlEncode = '\/guang\/star\/collocation';
if (result.code === 400) {
if (req.body.pageType === '1') {
let urlEncode = '\/guang\/star\/collocation';
let url = '/guang/star/collocation';
result.data = `${url}?openby:yohobuy={"action":"go.weblogin","params":{"jumpurl":{"url":"http:\/\/m.yohobuy.com${urlEncode}","param":{"from":"app"}},"requesturl":{"url":"","param":{}},"priority":"N"}}`; // eslint-disable-line
} else {
let urlEncode = '\/guang\/star\/detail';
let url = `/guang/star/detail?tag=${req.body.tag}`;
result.data = `${url}&openby:yohobuy={"action":"go.weblogin","params":{"jumpurl":{"url":"http:\/\/m.yohobuy.com${urlEncode}","param":{"from":"app","tag":"${req.body.tag}"}},"requesturl":{"url":"","param":{}},"priority":"N"}}`; // eslint-disable-line
}
}
res.json(result);
... ... @@ -172,11 +185,11 @@ exports.setFavorite = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.detail = (req, res) => {
let detail = (req, res) => {
res.render('star/detail', _.assign({
isStarDetailPage: true,
page: 'detail-list'
}, processPublicData(req, req.query.tag, false)));
}, _processPublicData(req, req.query.tag, false)));
};
/**
... ... @@ -185,10 +198,11 @@ exports.detail = (req, res) => {
* @param {[object]} res
* @return {[type]}
*/
exports.detailList = (req, res, next) => {
let detailList = (req, res, next) => {
let params = req.query;
let uid = req.cookies.guangStarUid || 0;
starModel.getDetailData(params).then((result) => {
starModel.getDetailData(params, uid).then((result) => {
res.render('star/list', _.assign({
layout: false,
params: params,
... ... @@ -197,3 +211,14 @@ exports.detailList = (req, res, next) => {
}));
}).catch(next);
};
module.exports = {
index,
getIndexHtml,
special,
collocation,
collocationList,
setFavorite,
detail,
detailList
};
... ...
... ... @@ -24,7 +24,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
}));
// router
... ...
... ... @@ -4,24 +4,17 @@
* @date: 2016/05/30
*/
'use strict';
const library = '../../../library';
const utils = '../../../utils';
const contentCodeConfig = require('../../../config/content-code');
const resourcesProcess = require(`${utils}/resources-process`);
const ServiceAPI = require(`${library}/api`).ServiceAPI;
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const logger = require(`${library}/logger`);
const camelCase = require(`${library}/camel-case`);
const helpers = require(`${library}/helpers`);
const logger = global.yoho.logger;
const camelCase = global.yoho.camelCase;
const helpers = global.yoho.helpers;
const _ = require('lodash');
const url = require('url');
const qs = require('querystring');
const serviceAPI = new ServiceAPI();
const api = new API();
const serviceAPI = global.yoho.ServiceAPI;
const api = global.yoho.API;
const contentCode = {
special: '89cc20483ee2cbc8a716dcfe2b6c7603'
};
const contentCode = contentCodeConfig.guang;
/**
* 获取资源位数据
... ... @@ -29,9 +22,10 @@ const contentCode = {
* @return {[array]}
*/
const _getResources = (page) => {
return serviceAPI.get('operations/api/v5/resource/get', sign.apiSign({
content_code: contentCode[page]
})).then((result) => {
return serviceAPI.get('operations/api/v5/resource/get', {
content_code: contentCode[page],
client_type: 'iphone'
}).then((result) => {
if (result && result.code === 200) {
return resourcesProcess(result.data);
} else {
... ... @@ -46,21 +40,24 @@ const _getResources = (page) => {
* @param {[array]} list
* @return {[array]}
*/
const _processIndexData = (list) => {
const _processIndexData = (dataList) => {
const formatData = {
ads: [],
starAvatar: [],
articles: []
};
list = list || {};
let list = dataList.data || {};
list = camelCase(list);
formatData.md5 = dataList.md5;
// 首页资源位数据处理
if (list.ads) {
_.forEach(list.ads.data, (data) => {
formatData.ads.push({
src: data.src,
src: data.src + '/q/80',
url: data.url
});
});
... ... @@ -80,20 +77,17 @@ const _processIndexData = (list) => {
_.forEach(data.ext.tags, (tags) => {
avatar.tags.push({
avatarUrl: `/guang/star/detail?tag=${tags.tagName}&openby:yohobuy{"action":"go.h5","params":{"id":"","share":"","shareparam":{},"islogin":"N","type":0,"updateflag":"N","url":"http://m.yohobuy.com/guang/star/detail","param":{"tag":"${tags.tagName}"}}}`, // eslint-disable-line
cover: tags.cover,
cover: tags.cover ? (tags.cover + '?imageView2/2/w/104/h/104/q/80') : tags.cover,
tagName: tags.tagName
});
});
let urlObj = url.parse(data.url);
let appUrl = `&openby:yohobuy{"action":"go.h5","params":{"id":"","share":"","shareparam":{},"islogin":"N","type":0,"updateflag":"N","url":"${ urlObj.protocol + '//' + urlObj.host + urlObj.pathname}","param":${JSON.stringify(qs.parse(urlObj.query))}}}`.replace(/\//g, '\/'); // eslint-disable-line
formatData.articles.push(_.merge({
id: data.id,
url: data.url + appUrl,
url: data.url,
title: data.title,
articeTxt: data.intro,
src: data.src,
src: data.src + '/q/80',
publishTime: helpers.dateFormat('MM月DD日 hh:mm', data.publishTime),
viewsNum: data.viewsNum
}, avatar));
... ... @@ -108,7 +102,7 @@ const _processIndexData = (list) => {
formatData.starAvatar.push({
url: url,
cover: data.cover
cover: data.cover ? (data.cover + '?imageView2/2/w/180/h/180/q/80') : data.cover
});
});
}
... ... @@ -116,52 +110,36 @@ const _processIndexData = (list) => {
return formatData;
};
const _processShareUrl = (post) => {
return `${post.share.url}&openby:yohobuy={"action":"go.share","params":{"pic":"${helpers.image(post.src, 640, 640)}","title":"${post.title}","url":"${post.share.url}","content":"潮流资讯,新鲜贩售,YOHO!Buy有货【逛】不停"}}`; // eslint-disable-line
};
/**
* 明星专题列表数据处理
* 明星专题列表及星搭配数据处理
* @param {[array]} list
* @param {[boolean]} flag 明星专题列表需要转换下日期格式
* @return {[array]}
*/
const _processDetailData = (list) => {
const _processGuangData = (list, flag) => {
const formatData = [];
list = list || [];
list = camelCase(list);
_.forEach(list, (data) => {
data.publishTime = helpers.dateFormat('MM月DD日 hh:mm', data.publishTime);
if (data.share && data.share.url) {
data.share.url = _processShareUrl(data);
_.forEach(list, (data, key) => {
if (flag) {
data.publishTime = helpers.dateFormat('MM月DD日 hh:mm', data.publishTime);
}
let urlObj = url.parse(data.url);
let appUrl = `&openby:yohobuy{"action":"go.h5","params":{"id":"","share":"","shareparam":{},"islogin":"N","type":0,"updateflag":"N","url":"${ urlObj.protocol + '//' + urlObj.host + urlObj.pathname}","param":${JSON.stringify(qs.parse(urlObj.query))}}}`.replace(/\//g, '\/'); // eslint-disable-line
data.url += appUrl;
formatData.push(data);
});
return formatData;
};
/**
* 星搭配文章列表数据处理
*/
const _processCollocationData = (list) => {
const formatData = [];
list = list || [];
list = camelCase(list);
_.forEach(list, (data) => {
if (data.isFavor === 'N') {
data.isCollected = false;
} else {
data.isCollected = true;
}
// data.src += '/q/80';
if (key < 4) {
data.islazy = true;
}
formatData.push(data);
});
... ... @@ -172,12 +150,13 @@ const _processCollocationData = (list) => {
* 星潮首页
*/
const getIndexData = () => {
return api.get('', sign.apiSign({
return api.get('', {
method: 'app.starClass.index',
code: '8adc27fcf5676f356602889afcfd2a8e'
})).then((result) => {
code: '8adc27fcf5676f356602889afcfd2a8e',
client_type: 'iphone'
}).then((result) => {
if (result && result.code === 200) {
return _processIndexData(result.data);
return _processIndexData(result);
} else {
logger.error('星潮教室首页数据返回 code 不是 200');
return {};
... ... @@ -188,18 +167,20 @@ const getIndexData = () => {
/**
* 明星专题
*/
const getDetailData = (params) => {
return api.get('', sign.apiSign({
const getDetailData = (params, uid) => {
return api.get('', {
method: 'app.starClass.lastTagArticle',
tag: params.tag,
page: params.page || 1,
size: 10
})).then((result) => {
size: 10,
uid: uid,
client_type: 'iphone'
}).then((result) => {
if (result && result.code === 200) {
if (params.page > result.data.totalPage) {
return '';
} else {
return _processDetailData(result.data.list);
return _processGuangData(result.data.list, true);
}
} else {
logger.error('明星专题文章数据返回 code 不是 200');
... ... @@ -216,18 +197,21 @@ const getSpecialData = () => {
return _getResources('special').then((result) => {
// 数据结构嵌套太深
_.forEach(result, (data) => {
_.forEach(result, (data, key) => {
_.map(data.data, (item) => {
if (!_.isObject(item)) {
return;
}
let urlObj = url.parse(item.url || '');
let appUrl = `&openby:yohobuy{"action":"go.h5","params":{"id":"","share":"","shareparam":{},"islogin":"N","type":0,"updateflag":"N","url":"${ urlObj.protocol + '//' + urlObj.host + urlObj.pathname}","param":${JSON.stringify(qs.parse(urlObj.query))}}}`.replace(/\//g, '\/'); // eslint-disable-line
item.url += appUrl;
item.src += '/q/80';
if (key < 4) {
item.islazy = true;
}
return item;
});
});
return result;
});
};
... ... @@ -236,13 +220,12 @@ const getSpecialData = () => {
* 星搭配
*/
const getCollocationListData = (params, uid) => {
return serviceAPI.get('guang/api/v5/article/getStarClassroomArticleList', sign.apiSign(Object.assign({
return serviceAPI.get('guang/api/v5/article/getStarClassroomArticleList', Object.assign({
limit: '20',
uid: uid
}, params))).then((result) => {
}, params)).then((result) => {
if (result && result.code === 200) {
return _processCollocationData(result.data.list.artList);
return _processGuangData(result.data.list.artList);
} else {
logger.error('获取星搭配文章列表返回 code 不是 200');
return [];
... ... @@ -253,23 +236,23 @@ const getCollocationListData = (params, uid) => {
const setFavorite = (params, uid) => {
if (!uid) {
return Promise.resolve({
code: 401,
code: 400,
message: '未登录'
});
}
return api.get('', sign.apiSign({
return api.get('', {
method: params.type === 'del' ? 'app.sns.cancelFavorBackCount' : 'app.sns.setFavorBackCount',
client_type: 'h5',
article_id: params.articleId,
uid: uid
}));
});
};
module.exports = {
getIndexData: getIndexData,
getSpecialData: getSpecialData,
getCollocationListData: getCollocationListData,
setFavorite: setFavorite,
getDetailData: getDetailData
getIndexData,
getSpecialData,
getCollocationListData,
setFavorite,
getDetailData
};
... ...
<div class="star-page yoho-page">
{{> star/head-tab}}
<div class="swiper-num"></div>
<div class="loading-tip">下拉刷新</div>
<div class="star-main">
<div class="main-content">
{{> star/index-html}}
</div>
</div>
</div>
... ...
... ... @@ -6,7 +6,11 @@
{{#if url}}
<li data-bp-id="guang_subjectList_{{title}}_false" class="buriedpoint">
<a href='{{url}}'>
<img class="lazy" data-original="{{image src 640 310}}" alt="{{alt}}"/>
{{#if islazy}}
<img src="{{image src 640 310}}" alt="{{alt}}"/>
{{else}}
<img class="lazy" data-original="{{image src 640 310}}" alt="{{alt}}"/>
{{/if}}
<p>{{title}}</p>
</a>
</li>
... ...
<ul class="head-tab">
<ul class="head-tab" {{#if notIndex}}style="position: fixed;"{{/if}}>
{{#each headTab}}
<li {{#if cur}} class="cur" {{/if}} ><a href="{{ url }}" data-bp-id="guang_tab_{{name}}_false">{{ name }}</a></li>
{{/each}}
... ...
<div class="loading-tip">下拉刷新</div>
<div class="star-wrap">
<div class="star-content">
{{#content}}
... ... @@ -12,7 +11,7 @@
<ul class="clearfix swiper-wrapper">
{{# starAvatar}}
<li class="swiper-slide">
<a class="rank-avatar" href='{{url}}' data-avatar="{{image cover 180 180}}"></a>
<a href='{{url}}' style="background-image: url({{image cover 180 180}})" class="star"></a>
</li>
{{/ starAvatar}}
</ul>
... ... @@ -51,7 +50,7 @@
<div class="artice-cont">
<p>{{articeTxt}}</p>
<div class="artice-imgs-area">
<img src="{{image src 266 266}}" />
<img data-original="{{image src 266 266}}" class="lazy" />
{{!-- <ul class="artice-imgs">
{{#each articeImg}}
... ...
... ... @@ -2,7 +2,13 @@
<li articleId="{{id}}" data-bp-id="guang_collocationList_{{title}}_false" class="buriedpoint">
{{#if src}}
<a href="{{url}}"><img class="lazy" src ="" data-original="{{image src 640 310}}" /></a>
<a href="{{url}}">
{{#if islazy}}
<img src ="{{image src 640 640}}"/>
{{else}}
<img class="lazy" src ="" data-original="{{image src 640 640}}" />
{{/if}}
</a>
{{/if}}
<div class="cont-area ">
... ... @@ -11,11 +17,11 @@
<div class="count-area">
<span class="time"><i class="iconfont time-ico">&#xe603;</i>{{publishTime}}</span>
<span class="see"><i class="iconfont see-ico">&#xe602;</i>{{viewsNum}}</span>
{{#if ../isApp}}
{{#if share.url}}
<a href="{{share.url}}" class="iconfont forward">&#xe600;</a>
{{/if}}
{{#if share.url}}
<a href="{{share.url}}" class="iconfont forward">&#xe600;</a>
{{/if}}
<span class="collection"><i class="iconfont collected-ico {{#isCollected}} collected {{/isCollected}}">&#xe605;</i></span>
</div>
</div>
... ...
/**
* 个人中心二维码 controller
* @author: weiqingting<qingting.wei@yoho.cn>
* @date: 2016/05/16
*/
'use strict';
const QRcodeModel = require('../models/qrcode');
exports.QRcode = (req, res, next) => {
let id = req.params.id || 0;
QRcodeModel.getQRcodeData(id, req.user.uid).then((result)=>{
if (result) {
result.ticks = result.ticks.map(item=>{
if (+item.ticket_type === 2) {
item.isgroup = true;
}
return item;
});
}
let vm = {
qrcodeData: result
};
res.render('QRcode', vm);
}).catch(next);
};
... ...
/**
* sub app coupon
* @author: lixia.zhang<lixia.zhang@yoho.cn>
* @date: 2016/05/31
*/
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
var app = express();
// set view engin
var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
app.on('mount', function(parent) {
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
});
app.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: ['./views/partial', `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
// router
app.use(require('./router'));
module.exports = app;
... ...
/**
* 个人中心二维码 model
* @author: weiqingting<qingting.wei@yoho.cn>
* @date: 2016/05/16
*/
'use strict';
const logger = global.yoho.logger;
var api = global.yoho.API;
exports.getQRcodeData = (id, uid) => {
return api.get('', {
method: 'app.SpaceOrders.getQrByOrderCode',
order_code: id,
uid: uid
}).then(result => {
if (result && result.code === 200) {
return result.data;
} else {
logger.error(`查看二维码ID: ${id} 接口返回数据错误`);
return false;
}
});
};
... ...
/**
* router of sub app product
* @author: weiqingting<qingting.wei@yoho.cn>
* @date: 2016/05/06
*/
'use strict';
const express = require('express');
const router = express.Router(); // eslint-disable-line
const cRoot = './controllers';
const personalController = require(`${cRoot}/qrcode`);
// 查看二维码
router.get('/QRcode/:id', personalController.QRcode);
module.exports = router;
... ...
<header id="yoho-header" class="yoho-header boys">
<a href="javascript:history.go(-1);" class="iconfont nav-back"></a>
<p class="nav-title">查看二维码</p>
</header>
{{#qrcodeData}}
<div class="qrcode yoho-page">
<h2 class='qrcode-title'>2016 非常潮流盛世 YO'HOOD门票(限量) {{ticks.length}}</h2>
<p class='qrcode-tip'>提示:凭借二维码入场,每场二维码只可使用一次,请妥善保管;</p>
<div class='qrcode-wrap'>
<p class='qrcode-pages'><a class='pagec'>1</a>/{{ticks.length}}</p>
<a class='prev chan'></a>
<a class='next chan'></a>
<div class="swiper-container">
<div class="swiper-wrapper">
{{#each ticks}}
<div class="swiper-slide">
<a class='qrcode-img'><img src="{{qr_image}}"></a>
<p><label class='t'>编号:</label>{{ticket_code}}</p>
{{#if isgroup}}
<p><label class='t'>座位号:</label>{{seat_no}}</p>
{{/if}}
<p><label class='t'>进场时间:</label>{{entrance_time}}</p>
</div>
{{/each}}
</div>
</div>
</div>
</div>
{{/qrcodeData}}
... ...
/**
* passport 验证策略注册
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/5/31
*/
... ... @@ -7,15 +8,69 @@
'use strict';
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 _ = require('lodash');
const md5 = require('md5');
const config = global.yoho.config;
const logger = global.yoho.logger;
const helpers = global.yoho.helpers;
const cookie = global.yoho.cookie;
const AuthHelper = require('./models/auth-helper');
let siteUrl = config.siteUrl.indexOf('//') === 0 ? 'http:' + config.siteUrl : config.siteUrl;
/**
* wechat登录
*/
// 本地登录
passport.use(new LocalStrategy({
usernameField: 'account',
passwordField: 'password',
passReqToCallback: true
}, (req, username, password, done) => {
let area = req.body.areaCode || '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 = area === '86' ? helpers.verifyAreaMobile(area + '-' + username) : true;
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);
AuthHelper.signinAes(area, username, password, shoppingKey).then((result) => {
if (result.code && result.code === 200 && result.data.uid) {
done(null, result.data);
} else {
done('账号或密码不正确', null);
}
}).catch(e => {
logger.error('call the signin service fail,', e);
done('登录失败,请稍后重试', null);
});
}));
// wechat 登录
passport.use('weixin', new WeixinStrategy({
clientID: config.thirdLogin.wechat.appID,
clientSecret: config.thirdLogin.wechat.appSecret,
... ... @@ -23,6 +78,35 @@ passport.use('weixin', new WeixinStrategy({
requireState: true,
authorizationURL: 'https://open.weixin.qq.com/connect/oauth2/authorize',
scope: 'snsapi_userinfo'
}, 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',
return_url: `${siteUrl}/passport/login/alipay/callback`
}), (profile, done) => {
done(null, profile);
});
... ...
/**
* 找回密码
* Created by Tao.Huang on 2016/6/12.
*/
'use strict';
const _ = require('lodash');
const helpers = global.yoho.helpers;
const service = require('../models/back-service');
const SIGN_IN = helpers.urlFormat('/passport/login');
/**
* 通过邮箱找回密码页面
*/
const indexEmailPage = (req, res) => {
res.render('back/email', Object.assign(
{
module: 'passport',
page: 'back-email',
title: '找回密码-通过邮箱'
}, {
backUrl: SIGN_IN,
headerText: '找回密码',
isPassportPage: true,
backEmail: true
}
));
};
/**
* 发送验证码到邮箱
*/
const sendCodeToEmailAPI = (req, res) => {
let email = req.body.email || '';
const ERR = {
code: 400,
message: '邮箱格式不正确,请重新输入',
data: ''
};
if (!helpers.verifyEmail(email)) {
res.json(ERR);
return;
}
service.sendCodeToEmailAsync(email)
.then(result => {
if (result.code === 200) {
result.data = helpers.urlFormat('/passport/back/success', { email: email });
}
res.json(result);
})
.catch(() => {
res.json(ERR);
});
};
/**
* 重新发送验证码到邮箱
*/
const resendCodeToEmailAPI = (req, res) => {
let email = req.query.email || '';
service.sendCodeToEmailAsync(email)
.then(result => {
if (_.isEmpty(result)) {
return Promise.rejected('重新发邮件失败');
}
res.json(result);
})
.catch(err => {
res.json({
code: 400,
message: err
});
});
};
/**
* 邮箱找回密码-返回成功页面
*/
const backSuccessByEmailPage = (req, res) => {
let email = req.query.email || '';
if (!helpers.verifyEmail(email)) {
res.redirect(400);
}
let domain = email.split('@')[1];
let emailUrl = `http://${domain === 'gmail.com' ? 'mail.google.com' : 'mail.'}${domain}`;
res.render('back/email-success', Object.assign({
module: 'passport',
page: 'back-email-success',
title: '找回密码-通过邮箱'
}, {
backUrl: helpers.urlFormat('/passport/back/email'),
headerText: '找回密码',
isPassportPage: true,
backEmailSuccess: true,
goEmail: emailUrl,
resendUrl: helpers.urlFormat('/passport/back/resendemail', { email: email })
}));
};
/**
* 根据邮箱修改密码
*/
const setNewPasswordByEmailAPI = (req, res) => {
let pwd = req.body.password || '';
let code = req.body.code || '';
let data = {
code: 200,
data: SIGN_IN
};
service.modifyPasswordByEmailAsyncAes(pwd, code)
.then(result => {
if (result.includes('history.back')) {
data.code = 400;
data.message = '修改失败';
}
res.json(data);
})
.catch(() => {
res.json(data);
});
};
/**
* 找回密码页面-通过手机号
*/
const indexMobilePage = (req, res, next) => {
service.getAreaDataAsync()
.then(result => {
res.render('back/mobile', Object.assign({
module: 'passport',
page: 'back-mobile',
title: '找回密码-通过手机号'
}, {
backUrl: SIGN_IN,
headerText: '找回密码',
isPassportPage: true,
backMobile: true,
countrys: result.data,
areaCode: '+86'
}));
})
.catch(next);
};
/**
* 发送手机验证码
*/
const sendCodeToMobileAPI = (req, res, next) => {
let phoneNum = req.body.phoneNum || '';
let areaCode = req.body.areaCode || '86';
let ERR = {
code: 400,
message: '输入手机号码出错'
};
if (!helpers.verifyMobile(phoneNum)) {
return res.json(ERR);
}
service.sendCodeToMobileAsync(phoneNum, areaCode)
.then(result => {
if (_.isEmpty(result) || result.code !== 200) {
ERR.message = '发送验证码出错';
res.json(ERR);
}
if (result.code === 200) {
return res.json({
code: 200,
data: helpers.urlFormat('/passport/back/mobilecode', {
phoneNum: phoneNum,
areaCode: areaCode
})
});
}
})
.catch(next);
};
/**
* 校验验证码页面
*/
const verifyCodeByMobilePage = (req, res) => {
let phoneNum = req.query.phoneNum || '';
let areaCode = req.query.areaCode || '86';
res.render('back/mobile-code', Object.assign({
module: 'passport',
page: 'back-code',
title: '找回密码-通过手机号'
}, {
backUrl: helpers.urlFormat('/passport/back/mobile'),
headerText: '找回密码',
isPassportPage: true,
backCode: true,
areaCode: areaCode,
phoneNum: phoneNum
}));
};
/**
* 校验手机验证码
*/
const verifyCodeByMobileAPI = (req, res, next) => {
let phoneNum = req.body.phoneNum || '';
let code = req.body.code || '';
let areaCode = req.body.areaCode || '86';
service.validateMobileCodeAsync(phoneNum, code, areaCode)
.then(result => {
if (result.code === 200) {
res.json({
code: 200,
data: helpers.urlFormat('/passport/back/backcode', {
phoneNum: phoneNum,
token: result.data.token,
areaCode: areaCode
})
});
} else {
res.json({
code: 400,
message: '验证码失败'
});
}
})
.catch(next);
};
/**
* 找回密码页面,设置新密码页面-手机
*/
const setNewPasswordByMobilePage = (req, res) => {
let phoneNum = req.query.phoneNum || '';
let token = req.query.token || '';
let areaCode = req.query.areaCode || '86';
let code = req.query.code || '';
if (!(code || (token && helpers.verifyMobile(phoneNum)))) {
res.redirect(400);
return;
}
res.render('back/new-password', Object.assign({
module: 'passport',
page: 'back-new-password',
title: '找回密码-输入新密码'
}, {
backUrl: SIGN_IN,
headerText: '找回密码',
isPassportPage: true,
backNewPwd: true,
phoneNum: phoneNum,
token: token,
areaCode: areaCode,
code: code
}));
};
/**
* 根据手机验证码修改密码
*/
const setNewPasswordByMobileAPI = (req, res, next) => {
let phoneNum = req.body.phoneNum || '';
let token = req.body.token || '';
let areaCode = req.body.areaCode || '86';
let newPwd = req.body.password || '';
service.modifyPasswordByMobileAsyncAes(phoneNum, token, newPwd, areaCode)
.then(result => {
if (result.code === 200) {
res.json({
code: 200,
data: SIGN_IN
});
} else {
res.json({
code: 400,
message: '修改密码失败'
});
}
})
.catch(next);
};
module.exports = {
indexEmailPage,
sendCodeToEmailAPI,
resendCodeToEmailAPI,
backSuccessByEmailPage,
setNewPasswordByEmailAPI,
indexMobilePage,
sendCodeToMobileAPI,
verifyCodeByMobilePage,
verifyCodeByMobileAPI,
setNewPasswordByMobilePage,
setNewPasswordByMobileAPI
};
... ...
/**
* 手机号绑定功能
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const helpers = global.yoho.helpers;
const RegService = require('../models/reg-service');
const BindService = require('../models/bind-service');
const AuthHelper = require('../models/auth-helper');
const _ = require('lodash');
const Sources = {
qq: 'QQ',
sina: '微博',
alipay: '支付宝',
wechat: '微信'
};
const bind = {
indexPage: (req, res) => {
let refer = req.get('Referer');
if (refer) {
res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
}
let openId = req.query.openId;
let sourceType = req.query.sourceType;
res.render('bind/index', {
bindIndex: true, // js标识
backUrl: helpers.urlFormat('/passport/login'), // 返回的URL链接
showHeaderImg: true, // 控制显示头部图片
isPassportPage: true, // 模板中模块标识
sourceType: sourceType, // 第三方登录来源
platform: Sources[sourceType],
isWechatLogin: sourceType === 'wechat',
openId: openId, // openId
areaCode: '+86', // 默认区号
countrys: RegService.getAreaData(), // 国别码
serviceUrl: 'http://chat8.live800.com/live800/chatClient/chatbox.jsp?companyID=620092&configID=149091&jid=8732423409&info=', // 在线客服
module: 'passport',
page: 'bind',
title: '绑定手机号'
});
},
codePage: (req, res) => {
let openId = req.query.openId;
let sourceType = req.query.sourceType;
let areaCode = req.query.areaCode || '86';
let isReg = req.query.isReg;
let phoneNum = req.query.phoneNum;
res.render('bind/code', {
backUrl: helpers.urlFormat('/passport/login'),
showHeaderImg: true,
isPassportPage: true,
sourceType: sourceType,
openId: openId,
isReg: isReg,
areaCode: areaCode,
phoneNum: phoneNum,
relateCode: isReg === '3',
bindCode: isReg !== '3',
module: 'passport',
page: isReg === '3' ? 'relate' : 'bind-code',
title: '验证手机'
});
},
bindCheck: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let openId = req.body.openId;
let areaCode = req.body.areaCode || '86';
let sourceType = req.body.sourceType;
if (_.isNumber(parseInt(phoneNum, 0)) && openId && areaCode && sourceType) {
BindService.bindCheck(phoneNum, openId, sourceType, areaCode).then(result => {
let data = {
code: result.code,
message: result.message,
data: {}
};
if (result.code === 200) {
let nextUrl = helpers.urlFormat('/passport/bind/code', {
isReg: result.data.is_register, // esline-disable-line
openId: openId,
sourceType: sourceType,
areaCode: areaCode,
phoneNum: phoneNum
});
data.data.isReg = result.data.is_register;
data.data.next = nextUrl;
} else {
data.data = result.data;
}
res.json(data);
}).catch(next);
} else {
res.json({
code: 400,
message: '',
data: ''
});
}
},
sendBindMsg: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let areaCode = req.body.areaCode;
if (req.xhr && _.isNumber(parseInt(phoneNum, 0)) && areaCode) {
BindService.sendBindMsg(areaCode, phoneNum).then(result => {
if (result && result.code) {
res.json(result);
} else {
res.json({ code: 400, message: '', data: '' });
}
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
checkBindMsg: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let code = req.body.code;
let areaCode = req.body.areaCode;
if (_.isNumber(parseInt(phoneNum, 0)) && code && areaCode) {
BindService.checkBindMsg(areaCode, phoneNum, code).then(result => {
if (result && result.code) {
res.json(result);
} else {
res.json({ code: 400, message: '', data: '' });
}
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
bindMobile: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let openId = req.body.openId;
let areaCode = req.body.areaCode || '86';
let sourceType = req.body.sourceType;
let code = req.body.code;
let password = req.body.password || '';
if (_.isNumber(parseInt(phoneNum, 0)) && openId && sourceType && areaCode && code) {
BindService.checkBindCode(areaCode, phoneNum, code).then(result => {
if (result && result.code && result.code === 200) {
return BindService.bindMobile(openId, sourceType, phoneNum, areaCode, password);
} else {
return { code: 400, message: '短信验证码错误', data: '' };
}
}).then(result => {
let refer = helpers.urlFormat('/passport/bind/success?type=bind');
if (result && result.code && result.code === 200 && result.data.uid) {
return AuthHelper.syncUserSession(result.data.uid, req, res).then(() => {
result.data.refer = refer;
return result;
});
} else {
return { code: 400, message: '绑定失败', data: '' };
}
}).then(result => {
res.json(result);
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
relateMobile: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let openId = req.body.openId;
let areaCode = req.body.areaCode || '86';
let sourceType = req.body.sourceType;
let code = req.body.code;
if (_.isNumber(parseInt(phoneNum, 0)) && openId && areaCode && sourceType && code) {
BindService.checkBindCode(areaCode, phoneNum, code).then(result => {
if (result && result.code && result.code === 200) {
return BindService.relateMobile(openId, sourceType, phoneNum, areaCode);
} else {
return { code: 400, message: '短信验证码错误', data: '' };
}
}).then(result => {
let refer = helpers.urlFormat('/passport/bind/success', { sourceType: sourceType });
if (result && result.code && result.code === 200 && result.data.uid) {
return AuthHelper.syncUserSession(result.data.uid, req, res).then(() => {
result.data.refer = refer;
return result;
});
} else {
return { code: 400, message: '关联失败', data: '' };
}
}).then(result => {
res.json(result);
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
passwordPage: (req, res) => {
let openId = req.query.openId;
let sourceType = req.query.sourceType;
let areaCode = req.query.areaCode || '86';
let phoneNum = req.query.phoneNum;
let code = req.query.code;
res.render('bind/password', {
module: 'passport',
page: 'bind-password',
bindPwd: true, // js标识
backUrl: helpers.urlFormat('/passport/login'), // 返回的URL链接
showHeaderImg: true, // 控制显示头部图片
isPassportPage: true, // 模板中模块标识
sourceType: sourceType, // 第三方登录来源
openId: openId, // openId
areaCode: areaCode, // 国别码
phoneNum: phoneNum, // 国别码
code: code // 验证码
});
},
successPage: (req, res) => {
let refer = req.cookies.refer;
let type = req.query.type;
refer = helpers.urlFormat('/?go=1');
res.render('bind/success', {
isPassportPage: true,
successTip: type === 'bind' ? '恭喜您,第三方账号绑定手机号码成功!' : '恭喜您,第三方账号关联手机号码成功!',
goUrl: refer,
module: 'passport',
page: 'bind-success',
title: '绑定手机号'
});
},
changeCheck: (req, res, next) => {
let phoneNum = req.body.phoneNum;
let areaCode = req.body.areaCode;
if (_.isNumber(parseInt(phoneNum, 0)) && areaCode) {
BindService.changeCheck(phoneNum, areaCode).then(result => {
res.json(result);
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
},
changeMobile: (req, res, next) => {
let uid = req.user.uid;
let phoneNum = req.body.phoneNum;
let areaCode = req.body.areaCode;
let code = req.body.code;
if (_.isNumber(parseInt(phoneNum, 0)) && uid && areaCode && code) {
BindService.changeMobile(uid, phoneNum, areaCode, code).then(result => {
res.json(result);
}).catch(next);
} else {
res.json({ code: 400, message: '', data: '' });
}
}
};
module.exports = bind;
... ...
... ... @@ -5,16 +5,17 @@
*/
'use strict';
const library = '../../../library';
const passport = require('passport');
const md5 = require('md5');
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 cookie = global.yoho.cookie;
const helpers = global.yoho.helpers;
const log = global.yoho.logger;
const config = global.yoho.config;
const RegService = require('../models/reg-service');
const AuthHelper = require('../models/auth-helper');
const loginPage = `${config.siteUrl}/passport/login/index`;
const loginPage = `${config.siteUrl}/passport/login`;
function doPassportCallback(openId, nickname, sourceType, req, res) {
let shoppingKey = cookie.getShoppingKey(req);
... ... @@ -26,11 +27,14 @@ function doPassportCallback(openId, nickname, sourceType, req, res) {
refer = `${config.siteUrl}/home`;
}
if (/sign|login/.test(refer)) {
if (/signin|login/.test(refer)) {
refer = `${config.siteUrl}/home`;
}
if (openId && nickname) {
return AuthHelper.signinByOpenID(nickname, openId, sourceType, shoppingKey).then((result) => {
if (result.code !== 200) {
return Promise.reject(result);
}
if (result.data['is_bind'] && result.data['is_bind'] === 'N') { //eslint-disable-line
return helpers.urlFormat('/passport/bind/index', {
openId: openId,
... ... @@ -45,38 +49,240 @@ function doPassportCallback(openId, nickname, sourceType, req, res) {
}).then((redirectTo) => {
return res.redirect(redirectTo);
});
} else {
return Promise.reject('missing third party login openId or nickname');
}
}
const wechat = {
const common = {
beforeLogin: (req, res, next) => {
let refer = req.query.refer;
if (!refer) {
refer = req.get('Referer');
}
refer && res.cookie('refer', encodeURI(refer), {
refer && !/signin|login|passport/.test(refer) && res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
next();
}
};
const local = {
loginPage: (req, res) => {
// 先清除cookie
res.clearCookie('LE' + md5('_LOGIN_EXPIRE'), {
domain: 'yohobuy.com'
});
// 设置登录有效时间30分钟, 防机器刷,cache不稳定,改为cookie
res.cookie('LE' + md5('_LOGIN_EXPIRE'), (new Date()).getTime() / 1000 + 1800);
// 清除cookie
res.clearCookie('_UID', {
domain: 'yohobuy.com'
});
res.clearCookie('_TOKEN', {
domain: 'yohobuy.com'
});
res.render('login', {
loginIndex: true, // 模板中使用JS的标识
// 返回的URL链接
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
showHeaderImg: true, // 控制显示头部图片
isPassportPage: true, // 模板中模块标识
registerUrl: '/passport/reg/index', // 注册的URL链接
aliLoginUrl: '/passport/login/alipay', // 支付宝快捷登录的URL链接
weiboLoginUrl: '/passport/login/sina', // 微博登录的URL链接
qqLoginUrl: '/passport/login/qq', // 腾讯QQ登录的URL链接
wechatLoginUrl: '/passport/login/wechat', // 微信登录的URL链接
internationalUrl: '/passport/international', // 国际号登录的URL链接
phoneRetriveUrl: '/passport/back/mobile', // 通过手机号找回密码的URL链接
emailRetriveUrl: '/passport/back/email', // 通过邮箱找回密码的URL链接
module: 'passport',
page: 'login',
title: '登录'
});
},
international: (req, res) => {
// 先清除cookie
res.clearCookie('LE' + md5('_LOGIN_EXPIRE'), {
domain: 'yohobuy.com'
});
// 设置登录有效时间30分钟, 防机器刷,cache不稳定,改为cookie
res.cookie('LE' + md5('_LOGIN_EXPIRE'), (new Date()).getTime() / 1000 + 1800);
// 清除cookie
res.clearCookie('_UID', {
domain: 'yohobuy.com'
});
res.clearCookie('_TOKEN', {
domain: 'yohobuy.com'
});
res.render('international', {
// 返回的URL链接
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
loginInternational: true, // 模板中使用JS的标识
isPassportPage: true, // 模板中模块标识
headerText: '登录',
areaCode: '+86', // 默认区号
countrys: RegService.getAreaData(), // 地区信息列表
module: 'passport',
page: 'international',
title: '国际账号登录'
});
},
login: (req, res, next) => {
passport.authenticate('local', (err, user) => {
if (err) {
res.json({
code: 400,
message: err,
data: ''
});
} else {
let refer = req.cookies.refer;
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');
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('weixin', {
state: uuid.v4()
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('weixin', (err, user) => {
if (err) {
log.error(`wechat authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user._json.nickname || user.displayName;
let openId = user._json.unionid || user.id;
doPassportCallback(openId, nickname, 'wechat', req, res).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) => {
passport.authenticate('weixin', (err, user) => {
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(openId, nickname, 'sina', req, res).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.id;
doPassportCallback(openId, nickname, 'qq', req, res).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._json.nickname || user.displayName;
let openId = user._json.unionid || user.id;
let nickname = user.realName;
let openId = user.userId;
doPassportCallback(openId, nickname, 'wechat', req, res).catch(next);
doPassportCallback(openId, nickname, 'alipay', req, res).catch(next);
})(req, res, next);
}
};
exports.common = common;
exports.local = local;
exports.wechat = wechat;
exports.sina = sina;
exports.qq = qq;
exports.alipay = alipay;
... ...
/**
* 注册
*
* @author Bi Kai<kai.bi@yoho.cn>
* @date 2016/06/23
*/
'use strict';
const _ = require('lodash');
const helpers = global.yoho.helpers;
const sign = global.yoho.sign;
const cookie = global.yoho.cookie;
const RegService = require('../models/reg-service');
const AuthHelper = require('../models/auth-helper');
let index = (req, res) => {
// 设置注册有效时间30分钟, 防机器刷
req.session._REG_EXPIRE = Date.now() + 1800000;
let refer = req.query.refer;
refer && res.cookie('refer', encodeURI(refer), {
domain: 'yohobuy.com'
});
res.render('reg/index', {
title: '注册',
backUrl: 'javascript:history.go(-1)', // eslint-disable-line
headerText: '注册', // 头部信息
isPassportPage: true, // 模板中模块标识
areaCode: '+86', // 默认的区号
countrys: RegService.getAreaData() // 地区信息列表
});
};
let verifyMobile = (req, res, next) => {
let data = {
code: 400,
message: '手机号已存在',
data: ''
};
let mobile = +req.body.phoneNum;
let area = +(req.body.areaCode || 86);
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号错误';
return res.json(data);
}
// 设置注册有效时间30分钟, 防机器刷
let expire = req.session._REG_EXPIRE;
if (!expire || expire < Date.now()) {
data.message = '非法请求';
return res.json(data);
}
// 向手机发送注册验证码
RegService.sendCodeToMobile(area, mobile).then((result) => {
if (!result.code) {
return res.json(data);
}
// 返回跳转到验证页面的链接
if (result.code === 200) {
let token = sign.makeToken(mobile);
result.data = helpers.urlFormat('/passport/reg/code', {
token: token,
phoneNum: mobile,
areaCode: area
});
}
return res.json(result);
}).catch(next);
};
let codeAction = (req, res, next) => {
let token = req.query.token;
let mobile = +req.query.phoneNum;
let area = +(req.query.areaCode || 86);
// 判断是否允许访问, 不允许则跳转到错误页面
if (!_.isString(token) || !_.isNumber(mobile) || !sign.verifyToken(mobile, token)) {
return next({
code: 403,
message: 'error token or mobile'
});
}
res.render('reg/code', {
page: 'code',
title: '注册-验证码',
backUrl: '/?go=1', // eslint-disable-line
headerText: '注册', // 头部信息
isPassportPage: true, // 模板中模块标识
areaCode: area, // 默认的区号
phoneNum: mobile, // 手机号
token: token, // 访问令牌
serviceUrl: 'http://chat8.live800.com/live800/chatClient/chatbox.jsp?companyID=620092&configID=149091&jid=8732423409&info=' // 在线客服
});
};
let sendCode = (req, res, next) => {
let data = {
code: 400,
message: '发送验证码失败',
data: ''
};
let mobile = +req.body.phoneNum;
let area = +(req.body.areaCode || 86);
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号错误';
return res.json(data);
}
// 设置注册有效时间30分钟, 防机器刷
let expire = req.session._REG_EXPIRE;
if (!expire || expire < Date.now()) {
data.message = '非法请求';
return res.json(data);
}
// 向手机发送注册验证码
RegService.sendCodeToMobile(area, mobile).then((result) => {
return result.code ? res.json(result) : res.json(data);
}).catch(next);
};
let verifyCode = (req, res, next) => {
let data = {
code: 400,
message: '验证码错误',
data: ''
};
let mobile = +req.body.phoneNum;
let area = +(req.body.areaCode || 86);
let code = +req.body.code;
/* 判断参数是否合法 */
if (!_.isNumber(mobile) || !_.isNumber(area) || !_.isNumber(code)) {
data.message = '手机号错误';
return res.json(data);
}
// 设置注册有效时间30分钟, 防机器刷
let expire = req.session._REG_EXPIRE;
if (!expire || expire < Date.now()) {
data.message = '非法请求';
return res.json(data);
}
// 验证注册的标识码是否有效
RegService.validMobileCode(area, mobile, code).then((result) => {
if (!result.code) {
return res.json(data);
}
// 返回跳转到设置密码的链接
if (result.code === 200) {
let token = sign.makeToken(mobile);
result.data = helpers.urlFormat('/passport/reg/password', {
token: token,
phoneNum: mobile,
areaCode: area
});
} else if (result.code === 404) {
result.message = '验证码错误'; // 统一验证提示
}
return res.json(result);
}).catch(next);
};
let passwordAction = (req, res, next) => {
let token = req.query.token;
let mobile = +req.query.phoneNum;
let area = +(req.query.areaCode || 86);
// 判断是否允许访问, 不允许则跳转到错误页面
if (!_.isString(token) || !_.isNumber(mobile) || !_.isNumber(area) || !sign.verifyToken(mobile, token)) {
return next({
code: 403,
message: 'error token or mobile'
});
}
res.render('reg/password', {
page: 'password',
title: '注册-设置密码',
backUrl: '/?go=1', // eslint-disable-line
headerText: '注册', // 头部信息
isPassportPage: true, // 模板中模块标识
areaCode: area, // 默认的区号
phoneNum: mobile, // 手机号
token: token // 访问令牌
});
};
let setPassword = (req, res, next) => {
let data = {
code: 400,
message: '密码格式不正确',
data: ''
};
let mobile = +req.body.phoneNum;
let area = +(req.body.areaCode || 86);
let password = req.body.password;
let token = req.body.token;
// 判断参数是否合法
if (!_.isString(token) || !_.isNumber(mobile) || !_.isNumber(area) || !password) {
data.message = '请求参数不合法';
return res.json(data);
}
// 判断是否允许访问
if (!sign.verifyToken(mobile, token)) {
data.message = '非法 token';
return res.json(data);
}
// 判断密码是否符合规则
if (!helpers.verifyPassword(password)) {
return res.json(data);
}
// 购物车key
let shoppingKey = cookie.getShoppingKey(req);
// 验证注册的标识码是否有效
RegService.regMobileAes(area, mobile, password, shoppingKey).then((result) => {
if (!result.code || result.code !== 200) {
return Promise.reject(result);
}
if (!result.data || !result.data.uid) {
return Promise.reject(result);
}
return AuthHelper.syncUserSession(result.data.uid, req, res);
}).then(() => {
// 返回跳转到来源页面
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = '/home';
}
if (/sign|login/.test(refer)) {
refer = '/home';
}
return res.json({
code: 200,
message: '注册成功',
data: {
session: refer,
href: refer
}
});
}).catch(next);
};
module.exports = {
index,
verifyMobile,
code: codeAction,
sendCode,
verifyCode,
password: passwordAction,
setPassword
};
... ...
... ... @@ -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
}));
... ...
/**
* 登录注册密码加密
* @author: wsl<shuiling.wang@yoho.cn>
* @date: 2016/07/07
*/
'use strict';
const crypto = require('crypto');
const aesPwd = (pwd) => {
let algorithm = 'aes-128-ecb';
let key = 'yoho9646yoho9646';
let clearEncoding = 'utf8';
let cipherEncoding = 'base64';
let iv = '';
let cipher = crypto.createCipheriv(algorithm, key, iv);
let cipherChunks = [];
cipherChunks.push(cipher.update(pwd, clearEncoding, cipherEncoding));
cipherChunks.push(cipher.final(cipherEncoding));
return cipherChunks.join('');
};
module.exports = {
aesPwd
};
... ...
'use strict';
const library = '../../../library';
const API = require(`${library}/api`).API;
const sign = require(`${library}/sign`);
const api = new API();
const aes = require('./aes-pwd');
const sign = global.yoho.sign;
const api = global.yoho.API;
class Auth {
static 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);
}
static signinAes(area, profile, password, shoppingKey) {
let param = {
method: 'app.passport.signinAES',
area: area,
profile: profile,
password: aes.aesPwd(password)
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.post('', param);
}
static signinByOpenID(nickname, openId, sourceType, shoppingKey) {
let param = {
nickname: nickname,
... ... @@ -20,7 +48,7 @@ class Auth {
param.shopping_key = shoppingKey;
}
return api.get('', sign.apiSign(param));
return api.get('', param);
}
static profile(uid) {
... ... @@ -29,7 +57,7 @@ class Auth {
method: 'app.passport.profile'
};
return api.get('', sign.apiSign(param));
return api.get('', param);
}
static syncUserSession(uid, req, res) {
... ... @@ -41,14 +69,16 @@ class Auth {
let uidCookie = `${data.profile_name}::${data.uid}::${data.vip_info.title}::${token}`;
res.cookie('_UID', uidCookie, {
domain: 'yohobuy.com'
domain: 'yohobuy.com',
expires: new Date(Date.now() + 2592000000) // 有效期一年
});
}
req.session._TOKEN = token; // esline-disable-line
req.session._LOGIN_UID = uid; // esline-disable-line
req.session._TOKEN = token;
req.session._LOGIN_UID = uid;
res.cookie('_TOKEN', token, {
domain: 'yohobuy.com'
}); // esline-disable-line
domain: 'yohobuy.com',
expires: new Date(Date.now() + 2592000000) // 有效期一年
});
});
}
}
... ...
/**
* Created by Tao.Huang on 2016/6/14.
*/
'use strict';
const api = global.yoho.API;
const logger = global.yoho.logger;
const aes = require('./aes-pwd');
const YOHOBUY_URL = 'http://www.yohobuy.com/';
/**
* 获取地区数据
*/
const getAreaDataAsync = () => {
return api.get('', {
method: 'app.passport.getArea'
}).then(result => {
if (result && result.code === 200) {
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;
} else {
logger.error('获取地区数据返回 code 不是 200');
return [];
}
});
};
/**
* 通过邮箱找回密码
*
* @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);
};
/**
* 根据邮箱验证码修改密码(调用新接口 采用AES密码加密)
*
* @param string pwd 新密码
* @param string code 邮箱验证码
*/
const modifyPasswordByEmailAsyncAes = (pwd, code) => {
return api.get('', {
code: code,
newPwd: aes.aesPwd(pwd),
method: 'app.register.resetPwdByCodeAES'
});
};
/**
* 通过手机找回密码
*
* @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'
});
};
const modifyPasswordByMobileAsyncAes = (mobile, token, newpwd, area) => {
return api.get('', {
mobile: mobile,
token: token,
newpwd: aes.aesPwd(newpwd),
area: area,
method: 'app.register.changepwdByMobileCodeAES'
});
};
module.exports = {
getAreaDataAsync,
sendCodeToEmailAsync,
modifyPasswordByEmailAsync,
modifyPasswordByEmailAsyncAes,
sendCodeToMobileAsync,
validateMobileCodeAsync,
modifyPasswordByMobileAsync,
modifyPasswordByMobileAsyncAes
};
... ...
/**
* Created by TaoHuang on 2016/6/14.
*/
'use strict';
const api = require('./back-api');
module.exports = api;
... ...
/**
* 注册数据接口
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const api = global.yoho.API;
const BindService = {
bindCheck(mobile, openId, sourceType, area) {
let params = {
method: 'app.passport.signCheck',
area: area,
mobile: mobile,
open_id: openId,
source_type: sourceType
};
return api.get('', params);
},
sendBindMsg(area, mobile) {
let params = {
method: 'app.passport.smsbind',
mobile: mobile,
area: area
};
return api.get('', params);
},
checkBindCode(area, mobile, code) {
return api.get('', {
method: 'app.register.validRegCode',
mobile: mobile,
area: area,
code: code
});
},
bindMobile(openId, sourceType, mobile, area, password, nickname) {
let params = {
method: 'app.passport.bind',
mobile: mobile,
open_id: openId,
source_type: sourceType,
area: area
};
if (password) {
params.password = password;
}
if (nickname) {
params.nickname = nickname;
}
return api.get('', params);
},
relateMobile(openId, sourceType, mobile, area) {
return api.get('', {
method: 'app.passport.relatedMobile',
mobile: mobile,
openId: openId,
source_type: sourceType,
area: area
});
},
changeCheck(mobile, area) {
return api.get('', {
method: 'app.passport.changeCheck',
mobile: mobile,
area: area
});
},
changeMobile(uid, mobile, area, code) {
return api.get('', {
method: 'app.passport.changeMobile',
mobile: mobile,
uid: uid,
code: code,
area: area
});
}
};
module.exports = BindService;
... ...
/**
* 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;
options = {};
}
options = options || {};
passport.Strategy.call(this);
this.name = 'alipay';
this._verify = verify;
this._options = options;
}
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) + this._options.key;
if (signType === 'MD5' && sign !== md5(signString)) {
this.error('alipay callback sign check fail');
this.fail('alipay callback sign check fail');
}
if (req.query.is_success === 'T') {
let user = {
userId: req.query.user_id,
realName: req.query.real_name,
email: req.query.email
};
this.success(user, null);
} else {
this.error('alipay login fail');
this.fail(req.error_code);
}
} else {
let params = _.assign(defaultOptions, this._options, options);
let signType = params.sign_type;
let key = params.key;
delete params.sign_type;
delete params.sign;
delete params.key;
let signString = paramsToRaw(params) + 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;
... ...
/**
* 注册数据接口
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const api = global.yoho.API;
const aes = require('./aes-pwd');
const RegService = {
getAreaData() {
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, // default choose
name: '中国'
}, {
areaCode: '+853',
selected: false,
name: '中国澳门'
}, {
areaCode: '+886',
selected: false,
name: '中国台湾'
}, {
areaCode: '+852',
selected: false,
name: '中国香港'
}];
},
sendCodeToMobile(area, mobile) {
let params = {
method: 'app.register.sendRegCodeToMobile',
area: area,
mobile: mobile
};
return api.post('', params);
},
validMobileCode(area, mobile, code) {
let params = {
method: 'app.register.validRegCode',
area: area,
mobile: mobile,
code: code
};
return api.post('', params);
},
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);
},
regMobileAes(area, mobile, password, shoppingKey) {
let params = {
method: 'app.passport.registerAES',
area: area,
profile: mobile,
password: aes.aesPwd(password)
};
if (shoppingKey) {
params.shopping_key = shoppingKey;
}
return api.post('', params);
}
};
module.exports = RegService;
... ...
... ... @@ -9,10 +9,88 @@
const express = require('express');
const cRoot = './controllers';
const login = require(cRoot + '/login');
const back = require(cRoot + '/back');
const bind = require(cRoot + '/bind');
const reg = require(cRoot + '/reg');
const router = express.Router(); // eslint-disable-line
router.get('/login/wechat', login.wechat.beforeLogin, login.wechat.login); // 登录
router.get('/login/wechat/callback', login.wechat.callback);
// 兼容老的路由
router.get('/signin.html', login.common.beforeLogin, login.local.loginPage);
router.get('/reg.html', reg.index);
router.get('/login.html', login.common.beforeLogin, login.local.international);
// 登出
router.get('/passport/signout/index', login.local.logout);
// 登录页面
router.get('/passport/login', login.common.beforeLogin, login.local.loginPage);
router.get('/passport/international', login.common.beforeLogin, login.local.international);
// 本地登录
router.post('/passport/login/auth', login.local.login);
// 微信登录
router.get('/passport/login/wechat', login.common.beforeLogin, login.wechat.login);
router.get('/passport/login/wechat/callback', login.wechat.callback);
// sina登录
router.get('/passport/login/sina', login.common.beforeLogin, login.sina.login);
router.get('/passport/login/sina/callback', login.sina.callback);
// qq登录
router.get('/passport/login/qq', login.common.beforeLogin, login.qq.login);
router.get('/passport/login/qq/callback', login.qq.callback);
// 支付宝登录
router.get('/passport/login/alipay', login.common.beforeLogin, login.alipay.login);
router.get('/passport/login/alipay/callback', login.alipay.callback);
// alipay登录
router.get('/login/alipay', login.common.beforeLogin, login.alipay.login);
router.get('/login/alipay/callback', login.alipay.callback);
// 登录绑定
router.get('/passport/bind/index', bind.indexPage);
router.post('/passport/bind/bindCheck', bind.bindCheck);
router.get('/passport/bind/code', bind.codePage);
router.post('/passport/bind/sendBindMsg', bind.sendBindMsg);
router.post('/passport/bind/bindMobile', bind.bindMobile);
router.post('/passport/bind/relateMobile', bind.relateMobile);
router.get('/passport/bind/password', bind.passwordPage);
router.get('/passport/bind/success', bind.successPage);
router.post('/passport/bind/changeCheck', bind.changeCheck);
router.post('/passport/bind/changeMobile', bind.changeMobile);
/**
* 注册
*/
router.get('/passport/reg/index', reg.index);
router.post('/passport/reg/verifymobile', reg.verifyMobile);
router.get('/passport/reg/code', reg.code);
router.post('/passport/reg/sendcode', reg.sendCode);
router.post('/passport/reg/verifycode', reg.verifyCode);
router.get('/passport/reg/password', reg.password);
router.post('/passport/reg/setpassword', reg.setPassword);
/**
* 密码找回
*/
router.get('/passport/back/email', back.indexEmailPage);// 通过邮箱找回密码页面
router.post('/passport/back/sendemail', back.sendCodeToEmailAPI);// 发送邮箱验证码
router.get('/passport/back/resendemail', back.resendCodeToEmailAPI);// 重新发送邮箱验证码
router.get('/passport/back/success', back.backSuccessByEmailPage);// 邮箱找回密码-发送成功页面
router.get('/passport/back/mobile', back.indexMobilePage);// 输入手机号找回密码页面
router.get('/passport/back/mobilecode', back.verifyCodeByMobilePage);// 输入手机验证码页面
router.post('/passport/back/sendcode', back.sendCodeToMobileAPI);// 发送手机验证码
router.post('/passport/back/verifycode', back.verifyCodeByMobileAPI);// 校验手机验证码
router.get('/passport/back/backcode', back.setNewPasswordByMobilePage);// 设置新密码页面
router.post('/passport/back/passwordbyemail', back.setNewPasswordByEmailAPI);// 依据邮箱验证码修改密码
router.post('/passport/back/passwordbymobile', back.setNewPasswordByMobileAPI);// 依据手机验证码修改密码
module.exports = router;
... ...
<div class="back-email-success-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<p class="tip">验证邮件已发送至你的邮箱</p>
<p class="sub-tip">请在24小时内通过邮件内的链接设置新密码</p>
<a class="go-email btn" href={{goEmail}}>去邮箱看看</a>
<a id="resend" class="resend" data-url={{resendUrl}}>重新发送邮件</a>
</div>
</div>
... ...
<div class="back-email-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<div class="input-container row has-clear">
<input id="email" class="input email" type="text" placeholder="请输入邮箱" autocomplete="off">
</div>
<span id="btn-sure" class="btn btn-sure disable row">确定</span>
</div>
</div>
... ...
<div class="back-code-page passport-page yoho-page">
{{> passport/code}}
</div>
... ...
<div class="back-mobile-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
{{> passport/country_list}}
<div class="input-container phone-container row has-clear">
<span id="area-code" class="area-code">{{areaCode}}</span>
<input id="phone-num" class="input phone-num" type="text" placeholder="手机号">
</div>
<span id="btn-next" class="btn btn-next disable row">下一步</span>
</div>
</div>
... ...
<div class="back-new-password-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<div class="input-container row has-eye">
<input id="pwd" class="input pwd" type="text" placeholder="请输入新密码" autocomplete="off" maxlength="20">
</div>
<span id="btn-ok" class="btn btn-ok disable">完成</span>
</div>
{{#if phoneNum}}
<input id="phone-num" type="hidden" value={{phoneNum}}>
<input id="area-code" type="hidden" value={{areaCode}}>
<input id="token" type="hidden" value={{token}}>
{{/if}}
{{# code}}
<input id="email-code" type="hidden" value={{.}}>
{{/ code}}
</div>
... ...
<div class="reg-code-page passport-page yoho-page">
<input type="hidden" id="isReg" value="{{isReg}}">
<input type="hidden" id="openId" value="{{openId}}">
<input type="hidden" id="sourceType" value="{{sourceType}}">
<input type="hidden" id="nickname" value="{{nickname}}">
{{> passport/code}}
</div>
\ No newline at end of file
... ...
<div class="bind-page passport-page yoho-page">
<input type="hidden" id="openId" value="{{openId}}">
<input type="hidden" id="sourceType" value="{{sourceType}}">
<input type="hidden" id="nickname" value="{{nickname}}">
{{> passport/header}}
<div class="content">
{{#if isWechatLogin}}
<p class="bind-tip">您正在使用{{platform}}快捷登录,请绑定手机号</p>
{{^}}
<p class="bind-tip">正在使用{{platform}}登录</p>
{{/if}}
{{> passport/country_list}}
<div class="input-container phone-container row has-clear">
<span id="area-code" class="area-code">{{areaCode}}</span>
<input id="phone-num" class="input phone-num" type="text" placeholder="手机号">
</div>
<span id="btn-next" class="btn btn-next disable row">发送验证码</span>
{{#if isWechatLogin}}
<div class="other-login">
<a href="/passport/login/index">
用其他方式登录
</a>
</div>
{{/if}}
<div class="tip">
<p>
登录注册中遇到问题?请联系客服
</p>
<a href="{{serviceUrl}}">
<span class="iconfont icon">&#xe63c;</span>
在线客服
</a>
</div>
</div>
</div>
\ No newline at end of file
... ...
<div class="bind-password-page passport-page yoho-page">
<input type="hidden" id="openId" value="{{openId}}">
<input type="hidden" id="sourceType" value="{{sourceType}}">
<input type="hidden" id="nickname" value="{{nickname}}">
{{> passport/header}}
<div class="content">
{{#if bindPwd}}
<div class="bind-tip">
<p class="title">绑定成功!</p>
您以后还可以使手机号+密码的形式登录有货哦!
</div>
{{/if}}
<div class="input-container row has-eye">
<input id="pwd" class="input pwd" type="text" placeholder="请输入密码" autocomplete="off" maxlength="20">
</div>
<span id="btn-sure" class="btn btn-sure disable row">确定</span>
</div>
<input id="phone-num" type="hidden" value={{phoneNum}}>
<input id="area-code" type="hidden" value={{areaCode}}>
<input id="token" type="hidden" value={{token}}>
<input id="code" type="hidden" value={{code}}>
</div>
\ No newline at end of file
... ...
<div class="success-page passport-page yoho-page">
<div class="success-icon">
</div>
<div class="success-tip">
{{{successTip}}}
</div>
<a class="go" href="{{goUrl}}">
立即购物
</a>
</div>
\ No newline at end of file
... ...
<div class="login-international-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
{{> passport/country_list}}
<div class="input-container phone-container row has-clear">
<span id="area-code" class="area-code">{{areaCode}}</span>
<input id="phone-num" class="input phone-num" type="text" placeholder="手机号" value={{phoneNum}}>
</div>
<div class="input-container row has-eye">
<input id="pwd" class="pwd input" type="password" placeholder="密码">
</div>
<span id="btn-login" class="btn btn-login disble row">登录</span>
</div>
</div>
\ No newline at end of file
... ...
<div class="login-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<div class="acc-container input-container row has-clear">
<div class="yoho-logo"></div>
<input id="account" class="input account" type="text" placeholder="手机号/邮箱" autocomplete="off" value={{account}}>
</div>
<div class="input-container row has-eye">
<input id="pwd" class="pwd input" type="password" placeholder="密码">
</div>
<span id="btn-login" class="btn btn-login disable">登录</span>
<p class="op-container">
<a class="go-register" href={{registerUrl}}>免费注册</a>
<span id="forget-pwd" class="forget-pwd">忘记密码</span>
</p>
<div class="third-party-login">
<div class="tp-link">
<a class="qq" href={{qqLoginUrl}}></a>
<a class="wechat" href={{wechatLoginUrl}}></a>
<a class="weibo" href={{weiboLoginUrl}}></a>
<a class="alipay" href={{aliLoginUrl}}></a>
</div>
</div>
<a class="international" href={{internationalUrl}}>International Customer</a>
<div class="login-tip">
<div class="info-icon"></div>
Yoho!Family账号可登录Yoho!Buy有货
</div>
<div id="retrive-pwd-mask" class="mask"></div>
<ul id="retrive-pwd-ways" class="retrive-pwd-ways">
<li>
<a href={{phoneRetriveUrl}}>通过手机找回密码</a>
</li>
<li>
<a href={{emailRetriveUrl}}>通过邮箱找回密码</a>
</li>
<li id="cancel-retrive">
取消
</li>
</ul>
</div>
</div>
\ No newline at end of file
... ...
<div class="reg-code-page passport-page yoho-page">
{{> passport/code}}
</div>
... ...
<div class="reg-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
{{> passport/country_list}}
<div class="input-container phone-container row has-clear">
<span id="area-code" class="area-code">{{areaCode}}</span>
<input id="phone-num" class="input phone-num" type="text" placeholder="手机号">
</div>
<span id="btn-next" class="btn btn-next disable row">下一步</span>
<p class="register-tip">YOHO!Family账号可登录Yoho!Buy有货、YOHO!Boys、YOHO!Girls及SHOW</p>
</div>
</div>
... ...
<div class="reg-password-page passport-page yoho-page">
{{> passport/header}}
<div class="content">
<div class="input-container row has-eye">
<input id="pwd" class="input pwd" type="text" placeholder="请输入密码" autocomplete="off" maxlength="20">
</div>
<span id="btn-sure" class="btn btn-sure disable row">确定</span>
</div>
<input id="phone-num" type="hidden" value="{{phoneNum}}">
<input id="area-code" type="hidden" value="{{areaCode}}">
<input id="token" type="hidden" value="{{token}}">
</div>
... ...
{{> passport/header}}
<div class="content">
<div class="text-container">
验证码已发送至
<span class="phone">
+{{areaCode}} {{phoneNum}}
</span>
</div>
<div class="input-container row has-clear">
<input id="captcha" class="input captcha" type="text" placeholder="验证码" maxlength="6" autocomplete="off">
<div id="captcha-tip" class="captcha-tip disable">重新发送 (60秒)</div>
</div>
<span id="btn-next" class="btn btn-next disable row">确定</span>
<div class="tip">
{{#if relateCode}}
注:关联的手机号不能用来登录此帐号
{{/if}}
</div>
<input id="phone-num" type="hidden" value={{phoneNum}}>
<input id="area-code" type="hidden" value={{areaCode}}>
<input id="token" type="hidden" value={{token}}>
</div>
... ...
<div class="select-container row">
<span class="select-title">国家和地区</span>
<select id="country-select" class="country-select select in-android-uc">
{{# countrys}}
<option value={{areaCode}} {{#if selected}}selected{{/if}}>{{name}}</option>
{{/ countrys}}
</select>
<div class="arrow-right"></div>
</div>
... ...
<div class="header">
<a class="go-back" href="{{#if backUrl}}{{backUrl}}{{^}}javascript:history.go(-1);{{/if}}"></a>
{{#showHeaderImg}}
<div class="img-header"></div>
{{/showHeaderImg}}
{{#headerText}}
<p class="title">{{.}}</p>
{{/headerText}}
</div>
\ No newline at end of file
... ...
... ... @@ -7,10 +7,11 @@
'use strict';
const mRoot = '../models';
const detail = require(`${mRoot}/detail`); // 商品详情 model
const intro = require(`${mRoot}/intro`); // 商品尺码信息 model
const headerModel = require('../../../doraemon/models/header'); // 头部model
const detailModel = require(`${mRoot}/detail`); // 商品详情 model
const introModel = require(`${mRoot}/intro`); // 商品尺码信息 model
const preference = require(`${mRoot}/preference`); // 商品偏好 model
const _ = require('lodash');
const detailRelated = require(`${mRoot}/consult-comment`); // 商品评论咨询 model
/**
* 商品基本信息
... ... @@ -18,24 +19,29 @@ const _ = require('lodash');
* @param {[type]} res [description]
* @return {[type]} [description]
*/
exports.index = (req, res) => {
var vipLevel = 0; // 用户等级 待处理
var uid = _.isEmpty(req.user.uid) ? null : req.user.uid;
exports.index = (req, res, next) => {
if (!req.params[0] || !req.params[1]) {
return next();
}
let uid = req.user.uid || 0;
let headerData = headerModel.setNav({
navTitle: '商品详情'
});
detail({
detailModel.getProductData({
id: req.params[0],
goodsId: req.params[1],
uid: uid,
vipLevel: vipLevel,
ua: req.get('user-agent') || ''
}).then((result) => {
res.render('detail/detail', {
pageHeader: headerData,
result: result,
module: 'product',
page: 'detail',
title: result.goodsName
title: result.goodsName,
pageFooter: true
});
});
}).catch(next);
};
/**
... ... @@ -44,14 +50,19 @@ exports.index = (req, res) => {
* @param {[type]} res [description]
* @return {[type]} [description]
*/
exports.intro = (req, res) => {
intro({
exports.intro = (req, res, next) => {
if (!req.params.productskn) {
return next();
}
introModel.getintroData({
productskn: req.params.productskn
}).then((result) => {
}, req).then((result) => {
res.render('detail/intro', {
result: result
result: result,
layout: false
});
});
}).catch(next);
};
/**
... ... @@ -62,13 +73,87 @@ exports.intro = (req, res) => {
*/
exports.preference = (req, res) => {
preference({
productskn: req.params.productskn,
yhchannel: req.params.yhchannel,
brandId: req.params.brandId
productskn: req.query.productSkn,
yhchannel: req.yoho.channel,
brandId: req.query.brandId
}).then((result) => {
res.render('detail/preference', {
result: result,
devEnv: true
});
res.render('detail/preference', Object.assign({
layout: false
}, result));
});
};
/**
* 购买评价页
*/
exports.comments = (req, res, next) => {
let headerData = headerModel.setNav({
navTitle: '购买评价'
});
detailRelated.comments(req.query).then((result) => {
res.render('detail/comments', Object.assign({
pageHeader: headerData,
pageFooter: true
}, result));
}).catch(next);
};
/**
* 购买咨询
*/
exports.consults = (req, res, next) => {
let headerData = headerModel.setNav({
navTitle: '购买咨询'
});
detailRelated.consults(req.query).then((result) => {
res.render('detail/consults', Object.assign({
pageHeader: headerData,
pageFooter: true
}, result));
}).catch(next);
};
/**
* 咨询表单页
*/
exports.consultform = (req, res, next) => {
let headerData = headerModel.setNav({
navTitle: '我要咨询'
});
if (!req.query.product_id) {
return next();
}
res.render('detail/consult-form', {
pageHeader: headerData,
productId: req.query.product_id,
formUrl: '/product/detail/consultsubmit',
pageFooter: true
});
};
/**
* 咨询提交接口
*/
exports.consultsubmit = (req, res, next) => {
let data = {
code: 400,
message: '请输入咨询内容',
data: ''
};
// 判断参数是否存在
if (!req.body.product_id || !req.body.content) {
return res.json(data);
}
detailRelated.addConsult(req.user.uid, req.body.product_id, req.body.content).then((result) => {
if (result) {
Object.assign(data, result);
}
return res.json(data);
}).catch(next);
};
... ...
... ... @@ -8,32 +8,40 @@
const outletModel = require('../models/outlet');
const headerModel = require('../../../doraemon/models/header');
const renderData = {
module: 'product'
const yhChannelEmpty = 0;
const willEndActivity = {
type: 2,
template: 'outlet/will-end',
page: 'outlet-will-end'
};
const willStartActivity = {
type: 3,
template: 'outlet/will-start',
page: 'outlet-will-start'
};
// 奥莱首页控制器
exports.index = (req, res) => {
exports.index = (req, res, next) => {
let headerData = headerModel.setNav({
navTitle: 'OUTLET',
navBtn: false
});
let categoryId = req.query.category_id;
let yhChannel = req.query.yh_channel || 0;
let yhChannel = req.query.yh_channel || yhChannelEmpty;
let contentcode = req.query.content_code;
outletModel.getContent(categoryId, yhChannel, contentcode).then(result => {
res.render('outlet', Object.assign({
page: 'outlet',
pageHeader: headerData
}, renderData, result));
});
}, result));
}).catch(next);
};
// 奥莱活动详情页
exports.activityDetail = (req, res) => {
exports.activityDetail = (req, res, next) => {
outletModel.getActivity(req.query.id).then(result => {
let headerData = headerModel.setNav({
navTitle: result.activityTitle,
... ... @@ -44,30 +52,42 @@ exports.activityDetail = (req, res) => {
page: 'outlet-detail',
pageHeader: headerData,
pageFooter: true
}, renderData, result));
});
}, result));
}).catch(next);
};
// 奥莱活动频道列表页
exports.activityList = (req, res) => {
exports.activityList = (req, res, next) => {
let headerData = headerModel.setNav({
navTitle: 'OUTLET',
navBtn: false
});
let categoryId = req.query.category_id;
let type = req.query.type || 2;
let template = parseInt(type, 10) === 2 ? 'outlet/will-end' : 'outlet/will-start';
let page = parseInt(type, 10) === 2 ? 'outlet-will-end' : 'outlet-will-start';
let type = req.query.type || willEndActivity.type;
let template;
let page;
if (!categoryId) {
throw new Error('No parent_id for OUTLET channel page!');
}
// 根据请求的类型确定要渲染的是即将结束页面还是上线预告页面
if (parseInt(type, 10) === willEndActivity.type) {
template = willEndActivity.template;
page = willEndActivity.page;
} else {
template = willStartActivity.template;
page = willStartActivity.page;
}
outletModel.getRecentActivity(type, categoryId).then(result => {
res.render(template, Object.assign({
page: page,
pageHeader: headerData,
pageFooter: true
}, renderData, result));
});
}, result));
}).catch(next);
};
... ...
'use strict';
const mRoot = '../models';
const recommendForYouModel = require(`${mRoot}/recommend-for-you`); // 领取优惠券 model
exports.userCenter = (req, res, next) => {
var udid = req.sessionID,
uid = req.user.uid || 0,
yhChannel = req.query.yh_channel || '1',
limit = 30;
recommendForYouModel.getPreference({
yh_channel: yhChannel,
udid: udid,
limit: limit,
rec_pos: '100004',
uid: uid
}).then((preferenceData) => {
if (preferenceData.code === 200) {
// 获取信息成功
}
res.render('recommend-for-you/index', {
layout: false,
result: preferenceData,
module: 'product',
page: 'recommend'
});
}).catch(next);
};
exports.cart = (req, res, next) => {
var udid = req.sessionID,
uid = req.user.uid || 0,
yhChannel = req.query.yh_channel || '1',
limit = 30;
recommendForYouModel.getPreference({
yh_channel: yhChannel,
udid: udid,
limit: limit,
rec_pos: '100003',
uid: uid
}).then((preferenceData) => {
if (preferenceData.code === 200) {
// 获取信息成功
}
res.render('recommend-for-you/index', {
layout: false,
result: preferenceData,
module: 'product',
page: 'recommend'
});
}).catch(next);
};
... ...
... ... @@ -27,24 +27,22 @@ const saleModel = require(`${mRoot}/sale`);
* @param {[string]} page js文件page名称
* @return {[type]}
*/
const processPublicData = (req, title, page) => {
let data = {};
let headerData = headerModel.setNav({
navTitle: title
});
data = {
const _processPublicData = (req, title, page, backUrl) => {
return {
channel: req.yoho.channel,
renderData: {
module: 'product',
page: page,
title: title,
pageHeader: headerData,
saleNav: true,
pageHeader: headerModel.setNav({
navTitle: title,
navBtn: false,
backUrl: backUrl || ''
}),
pageFooter: true
}
};
return data;
};
/**
... ... @@ -53,10 +51,34 @@ const processPublicData = (req, title, page) => {
* @param {[object]} res
* @return {[type]}
*/
exports.index = (req, res, next) => {
let params = processPublicData(req, 'SALE', 'sale');
let index = (req, res, next) => {
let refer = req.get('Referer') || `${global.yoho.config.siteUrl}/${req.cookies._Channel || ''}`;
let reqPath = req.baseUrl + req.path;
let backUrl = refer.indexOf(reqPath) === -1 ? refer : '';
if (['boys', 'girls', 'kids', 'lifestyle'].indexOf(req.yoho.channel) === -1) {
return next();
}
backUrl && res.cookie('saleRefer', backUrl, {
domain: 'm.yohobuy.com'
});
backUrl = backUrl || req.cookies.saleRefer;
let params = _processPublicData(req, 'SALE', 'sale', backUrl);
saleModel.getSaleData(params.channel).then((result) => {
// 扩展头部频道选择数据
Object.assign(params.renderData.pageHeader, {
saleNav: saleModel.saleNav(req.yoho.channel)
});
// 此处 channel 需要读取 cookies 的 channel
if (req.cookies._Channel) {
res.locals.pageChannel = {};
res.locals.pageChannel[req.cookies._Channel] = true;
}
res.render('sale/index', Object.assign(params.renderData, {
content: result,
floorHeader: {
... ... @@ -74,8 +96,8 @@ exports.index = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.breakingYards = (req, res, next) => {
let params = processPublicData(req, '断码区', 'break-code');
let breakingYards = (req, res, next) => {
let params = _processPublicData(req, '断码区', 'break-code');
saleModel.getBreakCodeData({
yhChannel: params.channel
... ... @@ -90,11 +112,13 @@ exports.breakingYards = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.discount = (req, res, next) => {
let params = processPublicData(req, '折扣专场', 'discount');
let discount = (req, res, next) => {
let params = _processPublicData(req, '折扣专场', 'discount');
saleModel.getDiscountData(params.channel).then((result) => {
res.render('sale/discount', Object.assign(params.renderData, result));
res.render('sale/discount', Object.assign(params.renderData, {
data: result
}));
}).catch(next);
};
... ... @@ -105,11 +129,11 @@ exports.discount = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.discountDetail = (req, res, next) => {
let discountDetail = (req, res, next) => {
let id = req.query.id;
let params = processPublicData(req, '', 'discount-detail');
let params = _processPublicData(req, '', 'discount-detail');
saleModel.getDiscountDetailData(id, params.channel).then((result) => {
saleModel.getDiscountDetailData(id, req.yoho.channel).then((result) => {
params.renderData.pageHeader.navTitle = result.title;
res.render('sale/discount-detail', Object.assign(params.renderData, result));
}).catch(next);
... ... @@ -121,8 +145,8 @@ exports.discountDetail = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.vip = (req, res, next) => {
let params = processPublicData(req, '会员专享', 'vip');
let vip = (req, res, next) => {
let params = _processPublicData(req, '会员专享', 'vip');
saleModel.getVipData(params.channel).then((result) => {
res.render('sale/vip', Object.assign(params.renderData, {
... ... @@ -137,7 +161,7 @@ exports.vip = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.filter = (req, res, next) => {
let filter = (req, res, next) => {
let params = Object.assign({}, req.query);
saleModel.getFilterData(params).then((result) => {
... ... @@ -156,12 +180,18 @@ exports.filter = (req, res, next) => {
* @param {[object]} res
* @return {[type]}
*/
exports.search = (req, res, next) => {
let search = (req, res, next) => {
let params = Object.assign({}, req.query);
// uid = 9239279
let uid = req.user.uid || 0;
// 断码区域必须传尺码
if (params.saleType === '1' && (!params.breakSort || !params.breakSize)) {
return res.render('sale/product', {
layout: false,
params: params
});
}
saleModel.getSearchData(params, uid).then((result) => {
let vipLevel = result[1].curLevel;
let vipObj = {};
... ... @@ -184,3 +214,13 @@ exports.search = (req, res, next) => {
}, vipObj));
}).catch(next);
};
module.exports = {
index,
breakingYards,
discount,
discountDetail,
vip,
filter,
search
};
... ...
... ... @@ -3,6 +3,7 @@
* @author: xuqi<qi.xu@yoho.cn>
* @date: 2016/05/06
*/
'use strict';
var express = require('express'),
path = require('path'),
... ... @@ -24,7 +25,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
}));
// router
... ...
/**
* 商品详情相关models
* @author: yyq<yanqing.yang@yoho.cn>
* @date: 2016/6/27
*/
'use strict';
const api = global.yoho.API;
const _ = require('lodash');
/**
* 获取默认咨询列表
*/
const _getCommonConsult = () => {
let params = {
method: 'app.consult.common'
};
return api.get('', params, {
code: 200
}).then(result => {
let data = {};
if (result.data) {
data.faq = result.data;
}
return data;
});
};
/**
* 处理评价列表数据
* @data {[object]} 评价列表原始数据
* @return {[object]}
*/
const _formatCommentsList = (data) => {
let comment = {
list: [],
total: 0
};
if (data.length) {
_.forEach(data, (value) => {
comment.list.push({
userName: value.nickname,
desc: `${value.color_name}/${value.size_name}`,
content: value.content,
time: value.create_time
});
comment.total = value.total;
});
}
return comment;
};
/**
* 处理咨询列表数据
* @data {[object]} 咨询列表原始数据
* @return {[object]}
*/
const _formatConsultsList = (data) => {
let list = [];
if (data.length) {
_.forEach(data, (value) => {
list.push({
question: value.ask,
time: value.ask_time,
answer: value.answer,
id: value.id,
isLike: value.is_like === 'Y',
like: _.toNumber(value.like),
isUseful: value.is_useful === 'Y',
useful: _.toNumber(value.useful)
});
});
}
return list;
};
/**
* 获取评价数据
* @id {[number]} 商品id
* @page {[number]} 页码
* @limit {[number]} 每页评价数量
* @return {[object]}
*/
const _getComments = (id, page, limit) => {
let params = {
method: 'app.comment.li',
product_id: id,
page: page ? page : 1,
limit: limit ? limit : 300
};
return api.get('', params, {
code: 200
}).then(result => {
let data = {};
if (result.data) {
Object.assign(data, _formatCommentsList(result.data));
}
return data;
});
};
/**
* 获取咨询数据
* @id {[number]} 商品id
* @page {[number]} 页码
* @limit {[number]} 每页咨询数量
* @return {[object]}
*/
const _getConsults = (id, page, limit) => {
let params = {
method: 'app.consult.li',
product_id: id,
page: page ? page : 1,
limit: limit ? limit : 300
};
return api.get('', params, {
code: 200
}).then(result => {
let data = {};
if (result.data && result.data.list) {
Object.assign(data, {
list: _formatConsultsList(result.data.list),
pageTotal: result.data.page_total,
total: result.data.total
});
}
return data;
});
};
/**
* 购买评价列表
* @param {[object]} 查询参数
* @return {[object]}
*/
let comments = (params) => {
return _getComments(params.product_id, 1, 60).then(result => {
let data = {};
if (result.list && result.list.length) {
if (result.total) {
_.set(data, 'pageHeader.navTitle', `购买评价(${result.total})`);
}
data.comments = result.list;
}
return data;
});
};
/**
* 购买咨询列表
* @params {[object]} 查询参数
* @return {[object]}
*/
let consults = (params) => {
return api.all([
_getCommonConsult(),
_getConsults(params.product_id, 1, 60)
]).then(result => {
let data = {
link: `/product/detail/consultform?product_id=${params.product_id}`
};
Object.assign(data, result[0]);
if (result[1].list && result[1].list.length) {
if (result[1].total) {
_.set(data, 'pageHeader.navTitle', `购买咨询(${result[1].total})`);
}
data.consults = result[1].list;
}
return data;
});
};
/**
* 购买咨询列表
* @uid {[number]} 用户id
* @productId {[number]} 商品id
* @content {[string]} 咨询内容
* @return {[object]}
*/
let addConsult = (uid, productId, content) => {
let params = {
method: 'h5.consult.add',
product_id: productId,
content: content,
uid: uid
};
return api.post('', params).then(result => {
if (result.code !== 200) {
return false;
}
return result;
});
};
module.exports = {
comments, // 商品详情相关-购买评价
consults, // 商品详情相关-购买咨询
addConsult // 商品详情相关-添加咨询
};
... ...