Authored by zhangxiaoru

面包屑导航

Showing 85 changed files with 4384 additions and 61 deletions

Too many changes to show.

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

<style>
.title {
padding: 10px 0;
}
.btn {
margin-bottom: 10px;
}
.demo-page {
padding: 10px;
}
... ... @@ -12,7 +20,7 @@
}
.demo-page .icon_lists span{
.demo-page .icon_lists span{
float: left;
width: 50px;
height: 30px;
... ... @@ -59,7 +67,7 @@
<span class="btn alert-btn">alert</span>
<span class="btn red confirm-btn">confirm</span>
<span class="btn white dialog-btn">dialog</span>
<p class="title">4. 提示文字或者某些链接文字颜色</p>
<span class="blue">提示文字</span>
<a class="blue">Link text</a>
... ... @@ -128,7 +136,27 @@
{{> icon/collection}}
{{> icon/bookmarks}}
</div>
</ul>
</ul>
</div>
<p style="margin-top: 20px;">14. 区域/选择地址组件</p>
<div style="padding: 5px; height: 300px;">
<div id="address"></div>
</div>
{{/ content}}
<p class="title">14. tips</p>
<div>
<button id="change" >show or hide tip</button>
</div>
<div id="tips" style="width: 200px;">
<input style="width: 100%;" type="text" name="" id="">
</div>
<p class="title">15. 头像</p>
<div>
<img class="avatar" src="http://devlup.com/wp-content/uploads/2013/07/images.jpg" alt="Braden Hamm">
</div>
</div>
\ No newline at end of file
... ...
/**
* passport 验证策略注册
* @author: jiangfeng<jeff.jiang@yoho.cn>
* @date: 2016/5/31
*/
'use strict';
const _ = require('lodash');
const passport = require('passport');
const WeixinStrategy = require('passport-weixin');
const SinaStrategy = require('passport-sina').Strategy;
const LocalStrategy = require('passport-local').Strategy;
const QQStrategy = require('passport-qq').Strategy;
const AlipayStrategy = require('./models/passport-alipay').Strategy;
const md5 = require('md5');
const AuthHelper = require('./models/auth-helper');
const config = global.yoho.config;
const helpers = global.yoho.helpers;
const cookie = global.yoho.cookie;
const logger = global.yoho.logger;
const cache = global.yoho.cache;
let siteUrl = config.siteUrl.indexOf('//') === 0 ? 'http:' + config.siteUrl : config.siteUrl;
// 本地登录
passport.use(new LocalStrategy({
usernameField: 'account',
passwordField: 'password',
passReqToCallback: true
}, (req, username, password, done) => {
let area = req.body.area || '86';
if (isNaN(parseInt(area, 0)) || _.isEmpty(username) || _.isEmpty(password)) {
logger.info(`【Passport Loginbad params, area:${area} account:${username} password:${password}`);
return done('登录参数错误', null);
}
let verifyEmail = helpers.verifyEmail(username);
let verifyMobile = helpers.verifyAreaMobile(username, area);
if (!verifyEmail && !verifyMobile) {
logger.info(`【Passport Loginbad account, email:${verifyEmail} mobile:${verifyMobile}`);
return done('登录账号格式错误', null);
}
let expire = req.cookies['LE' + md5('_LOGIN_EXPIRE')];
if (_.isEmpty(expire) || expire < (new Date()).getTime() / 1000) {
return done('页面停留时间过长,请刷新页面', null);
}
let shoppingKey = cookie.getShoppingKey(req);
let account = req.body.account;
let ip = req.ip;
let errorLoginKey = 'account_errorlogin_' + account;
let accountKey = 'account_signin_' + account;
let ipKey = 'ip_signin_' + ip;
let cacheGet = [cache.get(errorLoginKey), cache.get(accountKey), cache.get(ipKey)];
Promise.all(cacheGet).then(times => {
let errLoginTimes = parseInt(times[0], 0) || 0;
let accountTimes = parseInt(times[1], 0) || 0;
let ipTimes = parseInt(times[2], 0) || 0;
console.log(errLoginTimes);
if (accountTimes >= 10) {
done({ message: '您的账号已被暂时锁定,请稍后再试' }, null);
} else if (ipTimes >= 100) {
done({ message: '您尝试的次数过多,账号已被暂时锁定,请稍后再试' }, null);
} else {
return AuthHelper.signin(area, username, password, shoppingKey).then((result) => {
console.log(result);
if (result.code && result.code === 200 && result.data.uid) {
cache.del(errorLoginKey);
done(null, result.data);
} else {
errLoginTimes = errLoginTimes + 1;
accountTimes = accountTimes + 1;
ipTimes = ipTimes + 1;
cache.set(errorLoginKey, errLoginTimes);
cache.set(accountKey, accountTimes, 1800);
cache.set(ipKey, ipTimes, 3600);
// 再次校验
if (ipTimes >= 100) {
done({ message: '您尝试的次数过多,账号已被暂时锁定,请稍后再试' }, null);
} else if (accountTimes >= 10) {
done({ message: '您的账号已被暂时锁定,请稍后再试' }, null);
} else if (errLoginTimes >= 3) {
done({
message: `您输入的密码及账户名不匹配,
是否<a href="${helpers.urlFormat('/passport/back/index')}" target="_blank">忘记密码?</a>`,
needCaptcha: true
});
} else {
done({
message: `您输入的密码及账户名不匹配,
是否<a href="${helpers.urlFormat('/passport/back/index')}" target="_blank">忘记密码?</a>`,
needCaptcha: false
});
}
}
});
}
}).catch(e => {
logger.error('call the signin service fail,', e);
done('登录失败,请稍后重试', null);
});
}));
/**
* wechat登录
*/
passport.use('wechat', new WeixinStrategy({
clientID: config.thirdLogin.wechat.appID,
clientSecret: config.thirdLogin.wechat.appSecret,
callbackURL: `${siteUrl}/passport/login/wechat/callback`,
requireState: true,
scope: 'snsapi_login'
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
// sina 登录
passport.use('sina', new SinaStrategy({
clientID: '3739328910',
clientSecret: '9d44cded26d048e23089e5e975c93df1',
callbackURL: `${siteUrl}/passport/login/sina/callback`,
requireState: false
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
// qq 登录
passport.use('qq', new QQStrategy({
clientID: '100229394',
clientSecret: 'c0af9c29e0900813028c2ccb42021792',
callbackURL: `${siteUrl}/passport/login/qq/callback`,
requireState: false
}, (accessToken, refreshToken, profile, done) => {
done(null, profile);
}));
// alipay 登录
passport.use('alipay', new AlipayStrategy({
partner: '2088701661478015',
key: 'kcxawi9bb07mzh0aq2wcirsf9znusobw',
callbackURL: `${siteUrl}/passport/login/alipay/callback`
}), (profile, done) => {
done(null, profile);
});
... ...
/**
* 找回密码
* Created by Tao.Huang on 2016/6/12.
*/
'use strict';
const helpers = global.yoho.helpers;
const service = require('../models/back-service');
const passportHelper = require('../models/passport-helper');
const _ = require('lodash');
/**
* 找回密码主页面
*/
const index = (req, res, next) => {
service.indexPageDataAsync()
.then(result => {
res.render('back/index', Object.assign({
module: 'passport',
page: 'back-index',
title: '找回密码'
}, result));
})
.catch(next);
};
/**
* 校验用户输入信息,是否是已经注册的用户
*/
const validateInputAPI = (req, res, next) => {
let userInput = req.body.phoneNum || '';
let areaCode = (req.body.area || '86').replace('+', '');
service.validateEmailOrMobileAsync(userInput, areaCode)
.then(result => {
req.inputInfo = result;
next();
})
.catch(err => {
res.json({
code: 400,
message: err
});
});
};
/**
* 校验用户输入信息,是否是已经注册的用户
*/
const validateUserPage = (req, res, next) => {
let userInput = req.body.phoneNum || '';
let areaCode = (req.body.area || '86').replace('+', '');
service.validateEmailOrMobileAsync(userInput, areaCode)
.then(result => {
req.inputInfo = result;
next();
})
.catch(()=> {
res.redirect(helpers.urlFormat('/passport/back/index'));
});
};
const getUserInfoAPI = (req, res, next) => {
let inputInfo = req.inputInfo;
service.findUserAsync(inputInfo.type, inputInfo.phone, inputInfo.area)
.then(result => {
res.json(result);
})
.catch(next);
};
const sendCodePage = (req, res, next) => {
let inputInfo = req.inputInfo;
service.sendCodeToUserAsync(inputInfo.type, inputInfo.phone, inputInfo.area)
.then(result => {
console.log(result);
if (!(result.code && result.code === 200)) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
return next();
})
.catch(next);
};
const saveInSession = (req, res) => {
switch (req.inputInfo.type) {
case 'email':
{
req.session.email = req.inputInfo.phone;
res.redirect(helpers.urlFormat('/passport/back/sendEmail'));
break;
}
case 'mobile':
{
req.session.mobile = req.inputInfo.phone;
req.session.area = req.inputInfo.area;
res.redirect(helpers.urlFormat('/passport/back/verification'));
break;
}
default:
{
res.redirect(helpers.urlFormat('/passport/back/index'));
}
}
};
const sendBackMobileAPI = (req, res, next) => {
let mobile = req.body.mobile || '';
let area = req.body.area || '86';
service.sendCodeToMobileAsync(area, mobile)
.then(result => {
res.json(result);
})
.catch(next);
};
const validateMobileAPI = (req, res, next) => {
let mobile = req.body.mobile || '';
let area = req.body.area || '86';
const ERR = {code: 400, message: '验证失败'};
if (!passportHelper.validator.isAreaMobile(passportHelper.makeAreaMobile(area, mobile))) {
return res.json(ERR);
}
next();
};
const validateEmailInSession = (req, res, next) => {
let email = req.session.email || '';
if (!email) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
let isp = email.split('@')[1];
const mapperEmailISP = {
'yoho.cn': 'http://smail.yoho.cn'
};
req.body.emailUrl = mapperEmailISP[isp] || `http://mail.${isp}`;
next();
};
const sendEmailPage = (req, res, next) => {
passportHelper.getLeftBannerAsync()
.then(result => {
res.render('back/send-email', Object.assign({
module: 'passport',
page: 'back-send-email-ok',
title: '邮件发送成功'
}, {
sendEmail: {
coverHref: result.url,
coverImg: result.img,
email: req.body.emailUrl
}
}));
})
.catch(next);
};
const validateCodeByEmailPage = (req, res, next) => {
let code = req.query.code || '';
if (!_.isEmpty(req.mobileAuth)) {
return next();
}
service.checkEmailCodeAsync(code)
.then(result => {
if (!result) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
return next();
})
.catch(next);
};
const resetPasswordPage = (req, res, next) => {
let code = req.query.code || '';
passportHelper.getLeftBannerAsync()
.then(result => {
res.render('back/reset-pwd', Object.assign({
module: 'passport',
page: 'back-reset-pwd',
title: '重置密码'
}, {
resetPwd: Object.assign({
coverHref: result.url,
coverImg: result.img,
code: code
}, req.mobileAuth)
}));
})
.catch(next);
};
const verifyCodeByMobilePage = (req, res, next) => {
passportHelper.getLeftBannerAsync()
.then(result => {
res.render('back/verification', Object.assign({
module: 'passport',
page: 'back-verify-mobile-code',
title: '手机验证'
}, {
verification: {
coverHref: result.url,
coverImg: result.img,
mobile: req.body.mobile,
area: req.body.area,
verifyCode: req.body.verifyCode
}
}));
})
.catch(next);
};
const validateSuccessStatusPage = (req, res, next) => {
let successType = req.session.successType || '';
if (!successType) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
delete req.session.successType;
next();
};
const resetPwdSuccessPage = (req, res, next) => {
passportHelper.getLeftBannerAsync()
.then(result => {
res.render('back/reset-success', Object.assign({
module: 'passport',
page: 'back-index',
title: '重置密码成功'
}, {
resetSuccess: {
coverHref: result.url,
coverImg: result.img
}
}));
})
.catch(next);
};
const verifyCodeByMobileAPI = (req, res) => {
let mobile = req.param('mobile', '');
let area = req.param('area', '86');
let mobileCode = req.param('code', '');
const ERR = {
code: 400,
message: '验证码错误!',
data: helpers.urlFormat('/passport/back/index')
};
const session = req.session;
if (!mobileCode || mobile !== session.mobile || area !== session.area) {
return res.json(ERR);
}
service.verifyCodyByMobileAsync(area, mobile, mobileCode)
.then(result => {
res.json(result);
})
.catch(()=> {
res.json(ERR);
});
};
const validateExistCodePage = (req, res, next) => {
let code = req.query.code || req.body.code;
if (!code) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
next();
};
const validateCodeByMobilePage = (req, res, next) => {
let code = req.query.code || req.body.code;
let mobile = req.query.mobile || req.body.mobile;
let area = req.query.area || req.body.area;
let token = req.query.token || req.body.token;
let createdAt = req.query.createdAt || req.body.createdAt;
if (!mobile) {
req.mobileAuth = {};
return next();
}
let data = {
mobile: mobile,
area: area,
token: token,
createdAt: createdAt
};
code = new Buffer(code, 'base64').toString();
req.mobileAuth = service.authRequest(data, code);
next();
};
const validatePwdPage = (req, res, next) => {
let pwd = req.body.pwd || '';
if (!passportHelper.validator.isPassword(pwd)) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
next();
};
const updatePwdAPI = (req, res, next) => {
let code = req.body.code || '';
let mobileAuth = req.mobileAuth || {};
let newPassword = req.body.pwd || '';
service.updatePwdAsync(code, mobileAuth, newPassword)
.then(result => {
if (result.status) {
req.session.successType = result.type;
res.redirect(helpers.urlFormat('/passport/back/resetSuccess'));
} else {
res.redirect(helpers.urlFormat('/passport/back/index'));
}
})
.catch(next);
};
const validateMobileInSession = (req, res, next) => {
req.body.mobile = req.session.mobile || '';
req.body.verifyCode = req.session.verifyCode || '';
req.body.area = req.session.area || '';
if (!(req.body.mobile && req.body.verifyCode)) {
return res.redirect(helpers.urlFormat('/passport/back/index'));
}
next();
};
module.exports = {
index, // 首页
getUserInfoAPI, // 通过邮箱或手机号获得用户信息
sendCodePage, // 发送验证码到邮箱或者手机,然后跳转页面
saveInSession, // 保存状态到session中
sendEmailPage, // 发送邮件成功的页面
verifyCodeByMobilePage, // 验证手机验证码的页面
verifyCodeByMobileAPI, // 验证手机验证码
sendBackMobileAPI, // 重新发送验证码到手机
resetPasswordPage, // 重设密码页面
updatePwdAPI, // 重设密码接口
validateMobileAPI, // 验证手机号是否合法
resetPwdSuccessPage, // 重设密码成功页面
validateInputAPI, // 验证用户输入的邮箱或者手机是否合法,返回是json
validateUserPage, // 验证用户输入的邮箱或者手机是否合法,跳转是页面
validateEmailInSession, // 验证邮箱是否在session中
validateMobileInSession, // 验证手机是否在session中
validateCodeByEmailPage, // 验证邮箱验证码
validateCodeByMobilePage, // 验证手机验证码
validateSuccessStatusPage, // 验证重设密码状态
validateExistCodePage, // 验证参数是否存在code
validatePwdPage // 验证密码是否合法
};
... ...
/**
* 第三方登录后绑定
*
* @author: jiangfeng<jeff.jiang@yoho.cn>
*/
'use strict';
const bind = {
};
module.exports = bind;
... ...
/**
* Created by TaoHuang on 2016/6/18.
*/
'use strict';
const helpers = global.yoho.helpers;
const requiredAPI = (req, res, next) => {
let captchaToken = (req.body.verifyCode || '').toLowerCase();
if (captchaToken === req.session.captcha) {
return next();
} else {
return res.json({
code: 400,
message: '您输入的验证码不正确!'
});
}
};
const requiredPage = (req, res, next) => {
let captchaToken = (req.body.verifyCode || '').toLowerCase();
if (captchaToken === req.session.captcha) {
return next();
} else {
return res.redirect(helpers.urlFormat('/passport/back/index.html'));
}
};
module.exports = {
requiredAPI,
requiredPage
};
... ...
/**
* 登录
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const _ = require('lodash');
const passport = require('passport');
const uuid = require('uuid');
const md5 = require('md5');
const cookie = global.yoho.cookie;
const helpers = global.yoho.helpers;
const log = global.yoho.logger;
const config = global.yoho.config;
const cache = global.yoho.cache;
const AuthHelper = require('../models/auth-helper');
const PassportHelper = require('../models/passport-helper');
const loginPage = `${config.siteUrl}/passport/login/index`;
const SIGNIN_LEFT_BANNER_CODE = 'db350894e01e90eac55cd3a13ad77331';
// 第三方登录回调
function doPassportCallback(req, res, user) {
let shoppingKey = cookie.getShoppingKey(req);
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = config.siteUrl;
}
if (/sign|login/.test(refer)) {
refer = config.siteUrl;
}
if (user.openId && user.nickname) {
let signinByOpenID;
if (user.sourceType === 'wechat') {
// PC 的微信登录之前使用了 open_id, 所以需要特别的接口处理
signinByOpenID = AuthHelper.signinByWechat(
user.nickname, user.openId, user.unionId, user.sourceType, shoppingKey);
} else {
signinByOpenID = AuthHelper.signinByOpenID(
user.nickname, user.openId, user.sourceType, shoppingKey);
}
signinByOpenID.then((result) => {
if (result.code !== 200) {
return Promise.reject(result);
}
if (result.data['is_bind'] && result.data['is_bind'] === 'N') { //eslint-disable-line
return helpers.urlFormat('/passport/thirdlogin/index', {
openId: user.unionId || user.openId,
sourceType: user.sourceType,
refer: refer
});
} else if (result.code === 200 && result.data.uid) {
return AuthHelper.syncUserSession(result.data.uid, req, res).then(() => {
return refer;
});
}
}).then((redirectTo) => {
res.redirect(redirectTo);
}).catch(() => {
res.redirect(loginPage);
});
} else {
res.redirect(loginPage);
}
}
const common = {
beforeLogin: (req, res, next) => {
let refer = req.query.refer;
if (!refer) {
refer = req.get('Referer');
}
refer && res.cookie('refer', encodeURI(refer), {
domain: config.cookieDomain
});
next();
},
needCaptcha: (req, res, next) => {
let account = req.query.account;
let result = {code: 400, message: '', data: ''};
if (account) {
let errorLoginKey = 'account_errorlogin_' + account;
cache.get(errorLoginKey).then(errloginTimes => {
errloginTimes = parseInt(errloginTimes, 0) || 0;
console.log(errloginTimes);
if (!isNaN(errloginTimes) && errloginTimes >= 3) {
result.data = {needCaptcha: true};
}
res.json(result);
}).catch(next);
} else {
res.json(result);
}
}
};
const local = {
loginPage: (req, res) => {
// 设置登录有效时间30分钟, 防机器刷,cache不稳定,改为cookie
res.cookie('LE' + md5('_LOGIN_EXPIRE'), (new Date()).getTime() / 1000 + 1800);
let bindMobile = _.trim(req.query.bindMobile || '');
let bindArea = '+' + _.trim(req.query.bindArea || '86');
let areaArr = PassportHelper.getCountry();
let areaName = '';
if (bindArea) {
let area = areaArr.find((a) => {
return a.areaCode === bindArea;
});
areaName = area ? area.name : '';
}
PassportHelper.getLeftBannerAsync(SIGNIN_LEFT_BANNER_CODE).then(cover => {
res.render('login', {
loginPage: true,
passport: {
coverHref: cover.url,
coverImg: cover.img,
countryCode: bindArea,
countryName: areaName,
countryList: areaArr,
forgetPwd: helpers.urlFormat('/passport/back/index'),
fastReg: helpers.urlFormat('/reg.html'),
weixinLogin: helpers.urlFormat('/passport/autosign/wechat'),
qqLogin: helpers.urlFormat('/passport/autosign/qq'),
weiboLogin: helpers.urlFormat('/passport/autosign/sina'),
alipayLogin: helpers.urlFormat('/passport/autosign/alipay'),
doubanLogin: helpers.urlFormat('/passport/autosign/douban'),
renrenLogin: helpers.urlFormat('/passport/autosign/renren'),
bindMobile: bindMobile
},
module: 'passport',
page: 'login',
title: '用户登录'
});
});
},
login: (req, res, next) => {
passport.authenticate('local', (err, user) => {
if (err) {
res.json({
code: 400,
message: err.message,
data: {
needCaptcha: err.needCaptcha
}
});
} else {
let isRemember = req.body.isRemember;
let refer = req.cookies.refer;
if (isRemember) {
AuthHelper.rememberAccount({
area: req.body.areaCode || '86',
account: req.body.account,
password: req.body.password
}, req, res);
}
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = `${config.siteUrl}/home`;
}
if (/sign|login/.test(refer)) {
refer = `${config.siteUrl}/home`;
}
user.session = refer;
user.href = refer;
AuthHelper.syncUserSession(user.uid, req, res).then(() => {
res.json({
code: 200,
data: user
});
});
}
})(req, res, next);
},
logout: (req, res) => {
req.session = null;
res.clearCookie('_UID', {
domain: config.cookieDomain
});
res.clearCookie('_TOKEN', {
domain: config.cookieDomain
});
res.clearCookie('_SPK');
res.clearCookie('_g');
res.clearCookie('isRemember');
res.clearCookie('remem');
let refer = req.get('Referer') || config.siteUrl;
res.redirect(refer);
}
};
const wechat = {
login: (req, res, next) => {
req.session = req.session || {};
req.session.authState = uuid.v4();
return passport.authenticate('wechat', {
state: req.session.authState
})(req, res, next);
},
callback: (req, res, next) => {
if (req.session && req.session.authState && req.session.authState === req.query.state) {
passport.authenticate('wechat', (err, user) => {
if (err) {
log.error(`wechat authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
doPassportCallback(req, res, {
openId: user._json.openid,
unionId: user._json.unionid || user.id,
nickname: user._json.nickname || user.displayName,
sourceType: 'wechat',
rawUser: user
}).catch(next);
})(req, res, next);
} else {
return next(new Error('Auth State Mismatch'));
}
}
};
const sina = {
login: (req, res, next) => {
req.session = req.session || {};
req.session.authState = uuid.v4();
return passport.authenticate('sina', {
state: req.session.authState
})(req, res, next);
},
callback: (req, res, next) => {
if (req.session && req.session.authState && req.session.authState === req.query.state) {
passport.authenticate('sina', (err, user) => {
if (err) {
log.error(`sina authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.screen_name;
let openId = user.id;
doPassportCallback(req, res, {
openId: openId,
nickname: nickname,
sourceType: 'sina'
}).catch(next);
})(req, res, next);
} else {
return next(new Error('Auth State Mismatch'));
}
}
};
const qq = {
login: (req, res, next) => {
req.session = req.session || {};
req.session.authState = uuid.v4();
return passport.authenticate('qq', {
state: req.session.authState
})(req, res, next);
},
callback: (req, res, next) => {
if (req.session && req.session.authState && req.session.authState === req.query.state) {
passport.authenticate('qq', (err, user) => {
if (err) {
log.error(`qq authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.nickname;
let openId = user.openid;
doPassportCallback(req, res, {
openId: openId,
nickname: nickname,
sourceType: 'qq'
}).catch(next);
})(req, res, next);
} else {
return next(new Error('Auth State Mismatch'));
}
}
};
const alipay = {
login: (req, res, next) => {
return passport.authenticate('alipay')(req, res, next);
},
callback: (req, res, next) => {
passport.authenticate('alipay', (err, user) => {
if (err) {
log.error(`alipay authenticate error : ${JSON.stringify(err)}`);
return res.redirect(loginPage);
}
let nickname = user.realName;
let openId = user.userId;
doPassportCallback(req, res, {
openId: openId,
nickname: nickname,
sourceType: 'alipay'
}).catch(next);
})(req, res, next);
}
};
module.exports = {
common: common,
wechat: wechat,
local: local,
sina: sina,
qq: qq,
alipay: alipay
};
... ...
/**
* 注册控制器
*/
'use strict';
const _ = require('lodash');
const passportHelper = require('../models/passport-helper');
const regService = require('../models/reg-service');
const userService = require('../models/user-service');
const authHelper = require('../models/auth-helper');
let helpers = global.yoho.helpers;
let cache = global.yoho.cache;
let cookie = global.yoho.cookie;
let config = global.yoho.config;
let index = (req, res, next) => {
// 设置注册有效时间30分钟, 防机器刷
req.session._REG_EXPIRE = Date.now() + 1800000;
let refer = req.query.refer;
refer && res.cookie('refer', encodeURI(refer), {
domain: config.cookieDomain
});
regService.getRegData().then((result) => {
res.render('reg/index', {
title: '新用户注册',
passport: {
region: passportHelper.getCountry(),
location: '+86',
captchaUrl: helpers.urlFormat('/passport/images', {t: Date.now()}),
itemUrl: helpers.urlFormat('/help/', {category_id: 9}),
referUrl: refer,
loginUrl: helpers.urlFormat('/signin.html', {refer: refer}),
coverHref: result.url,
coverImg: result.img,
regBtnText: '立即注册'
}
});
}).catch(next);
};
let checkMobile = (req, res, next) => {
let data = {
code: 400
};
let mobile = +req.body.mobile;
let area = +req.body.area;
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号码格式不正确';
return res.json(data);
}
// 判断手机号是否检查超过指定次数
let key = 'checkmobilenum_' + passportHelper.makeAreaMobile(area, mobile);
cache.get(key).then((checkNum) => {
checkNum = +(checkNum || 0);
cache.set(key, ++checkNum).catch(next);
if (checkNum > 500) {
data.message = '检查次数太多';
return res.json(data);
}
// 判断用户是否存在
return userService.findByMobileAsync(area, mobile).then((user) => {
if (!_.isEmpty(user)) {
data.message = '手机号码已经存在';
return res.json(data);
}
data.code = 200;
return res.json(data);
});
}).catch(next);
};
let picCaptcha = (req, res, next) => {
let verifyCode = _.trim(req.body.verifyCode);
let picFlag = true; // TODO: 图形验证码校验
if (picFlag) {
return res.json({
code: 200,
message: '验证码正确'
});
}
return res.json({
code: 400,
message: '验证码错误'
});
};
let sendBindMsg = (req, res, next) => {
let data = {
code: 400,
message: '',
data: ''
};
let mobile = +req.body.mobile;
let area = +req.body.area;
let verifyCode = +req.body.verifyCode;
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号码格式不正确';
return res.json(data);
}
// 检查是否检查过
// let makeMobile = passportHelper.makeAreaMobile(area, mobile);
//
// if (req.session[`checkmobile_${makeMobile}`] !== makeMobile) {
// data.message = '发送失败';
// return res.json(data);
// }
// 校验是否发送过多
let sendCodeKey = `send_code_${area}_${mobile}`;
cache.get(sendCodeKey).then((sendCodeTimes) => {
if (!sendCodeTimes) {
sendCodeTimes = 0;
} else {
sendCodeTimes = +sendCodeTimes;
}
if (sendCodeTimes >= 10) {
data.message = '您已多次提交验证码,请尽快联系客服解决';
return res.json(data);
}
if (sendCodeTimes >= 5) {
data.message = '您收到的验证码短信已超过本日限定最多次数,请您耐心等待';
return res.json(data);
}
// TODO: 检测验证码不正确
// if (!PassportModel::verifyCode($verifyCode)) {
// $data['code'] = 400;
// $data['message'] = '图形验证码不正确';
// break;
// }
/* 向手机发送注册验证码 */
return regService.sendCodeToMobile(area, mobile).then((result) => {
return cache.set(sendCodeKey, sendCodeTimes + 1, 3600).then(() => {
if (result.code) {
return res.json(result);
} else {
data.message = '发送失败';
return res.json(data);
}
});
});
}).catch(next);
};
let msgCaptcha = (req, res, next) => {
let data = {
code: 400,
message: '',
data: ''
};
let area = +req.body.area;
let mobile = +req.body.mobile;
let code = +req.body.code; // 短信验证码
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号码格式不正确';
return res.json(data);
}
regService.validMobileCode(area, mobile, code).then((result) => {
if (result.code) {
return res.json(result);
} else {
data.message = '验证码错误';
return res.json(data);
}
}).catch(next);
};
let mobileRegister = (req, res, next) => {
let data = {
code: 400,
message: '',
data: ''
};
let area = +req.body.area;
let mobile = +req.body.mobile;
// 判断参数是否合法
if (!_.isNumber(mobile) || !_.isNumber(area)) {
data.message = '手机号码格式不正确';
return res.json(data);
}
/* 判断是否是有效的注册方式,防注册机刷 */
let regExpireTime = req.session._REG_EXPIRE;
if (!regExpireTime || regExpireTime < Date.now()) {
data.message = '注册超时';
return res.json(data);
}
// TODO: 检测验证码不正确
// $verifyCode = strtolower(trim($this->post('verifyCode'))); //图形验证码
// if (!PassportModel::verifyCode($verifyCode)) {
// $data['message'] = '验证码不正确';
// break;
// }
/* 判断密码是否符合规则 */
let code = +req.body.code; // 短信验证码
let password = req.body.password;
if (!helpers.verifyPassword(password)) {
data.message = '密码不正确';
return res.json(data);
}
/* IP仅允许点击注册500次/时 */
let ip = req.ip;
let ipKey = 'ip_register_' + ip;
cache.get(ipKey).then((ipTimes) => {
if (!ipTimes) {
ipTimes = 0;
} else {
ipTimes = +ipTimes;
}
if (ipTimes >= 500) {
data.message = '由于你IP受限无法注册';
return res.json(data);
}
return cache.set(ipKey, ipTimes + 1, 3600).then(() => {
/* 验证注册的标识码是否有效 */
return regService.validMobileCode(area, mobile, code).then((result) => {
if (!result.code || result.code !== 200) {
data.message = '验证码错误';
return res.json(data);
}
let shoppingKey = cookie.getShoppingKey(req);
/* 手机注册: 调用注册接口,ip限制计数 */
return regService.regMobile(area, mobile, password, shoppingKey).then((regResult) => {
if (!regResult.code || regResult.code !== 200) {
data.message = '注册失败';
return res.json(data);
}
// 返回跳转到来源页面
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = '/?go=1';
}
if (/sign|login/.test(refer)) {
refer = '/?go=1';
}
return authHelper.syncUserSession(regResult.data.uid, req, res).then(() => {
return res.json({
code: 200,
message: '注册成功',
data: {
href: helpers.urlFormat('/passport/reg/success', {
next: refer,
goShoppingUrl: config.siteUrl
})
}
});
});
});
});
});
}).catch(next);
};
let success = (req, res, next) => {
let goUrl = req.query.next || config.siteUrl;
let goShoppingUrl = req.query.goShoppingUrl || config.siteUrl;
regService.getRegData().then((result) => {
res.render('reg/success', {
title: '注册成功',
passport: {
goUrl: goUrl,
goShoppong: goShoppingUrl,
coverHref: result.url,
coverImg: result.img
}
});
}).catch(next);
};
module.exports = {
index,
success,
checkMobile,
picCaptcha,
sendBindMsg,
msgCaptcha,
mobileRegister
};
... ...
/**
* sub app channel
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
var express = require('express'),
path = require('path'),
hbs = require('express-handlebars');
var passport = require('passport');
var app = express();
// set view engin
var doraemon = path.join(__dirname, '../../doraemon/views'); // parent view root
app.on('mount', function(parent) {
delete parent.locals.settings; // 不继承父 App 的设置
Object.assign(app.locals, parent.locals);
});
app.set('views', path.join(__dirname, 'views/action'));
app.engine('.hbs', hbs({
extname: '.hbs',
defaultLayout: 'layout',
layoutsDir: doraemon,
partialsDir: [path.join(__dirname, 'views/partial'), `${doraemon}/partial`],
helpers: global.yoho.helpers
}));
require('./auth');
app.use(passport.initialize());
app.use(passport.session());
// router
app.use(require('./router'));
module.exports = app;
... ...
'use strict';
const md5 = require('md5');
const cache = global.yoho.cache;
const sign = global.yoho.sign;
const api = global.yoho.API;
const config = global.yoho.config;
const Auth = {
signin(area, profile, password, shoppingKey) {
let param = {
method: 'app.passport.signin',
area: area,
profile: profile,
password: password
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.post('', param);
},
signinByOpenID(nickname, openId, sourceType, shoppingKey) {
let param = {
nickname: nickname,
openId: openId,
source_type: sourceType,
method: 'app.passport.signinByOpenID'
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.get('', param);
},
signinByWechat(nickname, openId, unionId, sourceType, shoppingKey) {
let param = {
nickname: nickname,
openId: openId,
unionId: unionId,
source_type: sourceType,
method: 'app.passport.signinByWechat'
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.get('', param);
},
profile(uid) {
let param = {
uid: uid,
method: 'app.passport.profile'
};
return api.get('', param);
},
syncUserSession(uid, req, res) {
return Auth.profile(uid).then((userInfo) => {
let token = sign.makeToken(uid);
let data = userInfo.data;
if (data) {
let uidCookie = `{data.profile_name}::${data.uid}::${data.vip_info.title}::${token}`;
req.session._TOKEN = token;
req.session._LOGIN_UID = uid;
res.cookie('_UID', uidCookie, {
domain: config.cookieDomain
});
}
req.session._TOKEN = token; // esline-disable-line
req.session._LOGIN_UID = uid; // esline-disable-line
res.cookie('_TOKEN', token, {
domain: config.cookieDomain
}); // esline-disable-line
}).catch(console.log);
},
rememberAccount(accountInfo, req, res) {
let aWeek = (new Date()).getTime() / 1000 + 504000; // 504000-一周
let rememKey = md5(md5(accountInfo.account + accountInfo.password + accountInfo.area));
res.cookie('isRemember', true, aWeek);
res.cookie('remem', rememKey, aWeek);
if (!cache.get(rememKey)) {
cache.set(rememKey, accountInfo, aWeek);
}
}
};
module.exports = Auth;
... ...
/**
* Created by TaoHuang on 2016/6/15.
*/
'use strict';
const api = global.yoho.API;
const YOHOBUY_URL = 'http://www.yohobuy.com/';
/**
* 获取地区数据
*/
const getAreaDataAsync = () => {
return api.get('', {
method: 'app.passport.getArea'
}).then(result => {
result.data = result.data.map(value => {
value.areaCode = `+${value.area}`;
if (value.areaCode === '+86') {
value.selected = true;
} else {
value.selected = false;
}
delete value.area;
return value;
});
return result;
});
};
/**
* 通过邮箱找回密码
*
* @param string mail 邮箱地址
*/
const sendCodeToEmailAsync = (email) => {
return api.get('', {
method: 'app.register.backpwdByEmail',
email: email
});
};
/**
* 根据邮箱验证码修改密码(调用www.yohobuy.com接口)
*
* @param string pwd 新密码
* @param string code 邮箱验证码
*/
const modifyPasswordByEmailAsync = (pwd, code) => {
const options = {
url: `${YOHOBUY_URL}passport/back/update`,
form: {
pwd: pwd,
're-input': pwd,
code: code
},
timeout: 3000
};
return api._requestFromAPI(options);
};
/**
* 通过手机找回密码
*
* @param string mobile 手机号
* @param integer area 地区码ID
*/
const sendCodeToMobileAsync = (mobile, area) => {
return api.get('', {
mobile: mobile,
area: area,
method: 'app.register.sendBackpwdCodeToMobile'
});
};
/**
* 校验密码修改手机验证码
*
* @param string mobile 手机号
* @param string code 验证码
* @param integer area 地区码ID
*/
const validateMobileCodeAsync = (mobile, code, area) => {
area = area || 86;
return api.get('', {
mobile: mobile,
code: code,
area: area,
method: 'app.register.validBackpwdCode'
});
};
/**
* 根据手机验证码修改密码
*
* @param string mobile 手机号
* @param string token 验证手机验证码返回的token
* @param integer area 地区码ID
*/
const modifyPasswordByMobileAsync = (mobile, token, newpwd, area)=> {
return api.get('', {
mobile: mobile,
token: token,
newpwd: newpwd,
area: area,
method: 'app.register.changepwdByMobileCode'
});
};
/**
* 验证找回邮件code
*/
const checkEmailCodeAsync = (code) => {
return api.get('', {
code: code,
method: 'web.passport.checkCodeValid'
});
};
/**
* 根据邮箱code修改密码
*/
const modifyPasswordByEmailCodeAsync = (code, password) => {
return api.get('', {
code: code,
newPwd: password,
method: 'app.register.resetPwdByCode'
});
};
module.exports = {
getAreaDataAsync,
sendCodeToEmailAsync,
modifyPasswordByEmailAsync,
sendCodeToMobileAsync,
validateMobileCodeAsync,
modifyPasswordByMobileAsync,
checkEmailCodeAsync,
modifyPasswordByEmailCodeAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/27.
*/
'use strict';
/**
* 签名算法参考微信支付加密算法
* 参考链接:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
*
*/
const _ = require('lodash');
const SALT = '_+@#$%^';
/**
* 加密手机的数据,生成加密token
* @param data json
* @returns string
*/
const _packageObject = (data) => {
return _.keys(data).sort().map(key => `${key}=${data[key]}`).join('&').toUpperCase();
};
const _encodeMD5 = (str) => {
const md5 = require('md5');
return md5(str).toUpperCase();
};
/**
* 返回一个 md5加密后的字符串
* @param data json
*/
const makeToken = (data) => {
let saltData = Object.assign({}, data, {key: SALT});
let str = _packageObject(saltData);
return _encodeMD5(str);
};
/**
* 验证 token 是否一致
*/
const validateToken = (data, token) => {
let saltData = Object.assign({}, data, {key: SALT});
let str = _packageObject(saltData);
return _encodeMD5(str) === token;
};
module.exports = {
makeToken,
validateToken
};
... ...
/**
* Created by TaoHuang on 2016/6/14.
*/
'use strict';
const Promise = require('bluebird');
const co = Promise.coroutine;
const _ = require('lodash');
const moment = require('moment');
const helpers = global.yoho.helpers;
const api = require('./back-api');
const userService = require('./user-service');
const passportHelper = require('./passport-helper');
const backHelper = require('./back-helper');
const BACK_LEFT_BANNER_CODE = '3bbaf502c447a2ddad60879042e286d8'; // 找回密码左边的banner
/**
* 验证手机和邮箱输入正确性
*/
const validateEmailOrMobileAsync = (userInput, areaCode) => {
return new Promise(function(resolve, rejected) {
let result = {type: 'email', area: '', phone: ''};
if (passportHelper.validator.verifyEmail(userInput)) {
result.type = 'email';
result.area = '';
result.phone = userInput;
resolve(result);
} else if (passportHelper.validator.isAreaMobile(passportHelper.makeAreaMobile(areaCode, userInput))) {
result.type = 'mobile';
result.area = areaCode;
result.phone = userInput;
resolve(result);
} else {
rejected('输入信息出错!');
}
});
};
/**
* 查找用户
*/
const findUserAsync = (type, phone, area) => {
return co(function * () {
const MESSAGE = {
mobile: '您输入的手机号码尚未注册!',
email: '您输入的邮件账户尚未注册!',
ok: '验证成功'
};
const findBy = {
email: userService.findByEmailAsync,
mobile: (phone1, area1) => userService.findByMobileAsync(area1, phone1) // 交换参数
};
const OK = {code: 200, message: MESSAGE.ok};
const user = yield findBy[type](phone, area);
if (_.isEmpty(user)) {
return {
code: 402,
message: MESSAGE[type]
};
}
return OK;
})();
};
/**
* 发送验证码到用户
*/
const sendCodeToUserAsync = (type, mobile, areaCode) => {
let sendTo = {
email: api.sendCodeToEmailAsync,
mobile: api.sendCodeToMobileAsync
};
return sendTo[type](mobile, areaCode);
};
/**
* 发送找回手机号短信
*/
const sendCodeToMobileAsync = (areaCode, mobile) => {
return api.sendCodeToMobileAsync(mobile, areaCode);
};
/**
* 获得首页的数据
*/
const indexPageDataAsync = () => {
return co(function *() {
let banner = yield passportHelper.getLeftBannerAsync(BACK_LEFT_BANNER_CODE);
let countryList = passportHelper.getCountry();
return {
back: {
coverHref: banner.url,
coverImg: banner.img,
countryCode: 86,
countryName: '中国',
captchaUrl: helpers.urlFormat('/passport/images', {t: moment().unix()}),
countryList: countryList
}
};
})();
};
/**
* 验证手机验证码
*/
const verifyCodyByMobileAsync = (area, mobile, mobileCode) => {
const ERR = {
code: 400,
message: '验证码错误!',
data: helpers.urlFormat('/passport/back/index')
};
return api.validateMobileCodeAsync(mobile, mobileCode, area)
.then(result => {
if (!(result.code && result.code === 200)) {
return ERR;
}
let data = {
mobile: mobile,
area: area,
token: result.data.token,
createdAt: moment().unix()
};
data.code = new Buffer(backHelper.makeToken(data)).toString('base64');
return {
code: 200,
message: '验证成功',
data: helpers.urlFormat('/passport/back/backcode', data)
};
});
};
/**
* 手机 token 合法性验证
*/
const authRequest = (data, token) => {
if (!backHelper.validateToken(data, token)) {
return {};
}
let existTime = moment.duration(60, 'minutes').asSeconds();
let isExpired = (moment().unix() - data.createdAt) > existTime;
if (isExpired) {
return {};
} else {
return data;
}
};
/**
* 更新密码接口
*/
const updatePwdAsync = (emailToken, mobileToken, newPassword) => {
return co(function * () {
let result = {type: 'mobile', status: false};
const ERR = {type: 'unknown', status: false};
if (!_.isEmpty(mobileToken)) {
if (!mobileToken.mobile || mobileToken.uid) {
return ERR;
}
let mobile = mobileToken.mobile;
let area = mobileToken.area;
let token = mobileToken.token;
let modifyStatus = yield api.modifyPasswordByMobileAsync(mobile, token, newPassword, area);
if (!modifyStatus.code || modifyStatus.code !== 200) {
return ERR;
}
result.type = 'mobile';
result.status = true;
} else {
let modifyStatus = yield api.modifyPasswordByEmailCodeAsync(emailToken, newPassword);
if (!modifyStatus.code || modifyStatus.code !== 200) {
return ERR;
}
result.type = 'email';
result.status = true;
}
return result;
})();
};
/**
* 验证邮件验证码
*/
const checkEmailCodeAsync = api.checkEmailCodeAsync;
module.exports = {
validateEmailOrMobileAsync,
findUserAsync,
sendCodeToUserAsync,
sendCodeToMobileAsync,
indexPageDataAsync,
verifyCodyByMobileAsync,
authRequest,
updatePwdAsync,
checkEmailCodeAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
'use strict';
const serviceAPI = global.yoho.ServiceAPI;
/**
* 资源码找资源
*/
const getResourceAsync = resourceCode => {
return serviceAPI.get('/operations/api/v5/resource/get', {
content_code: resourceCode
});
};
module.exports = {
getResourceAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
'use strict';
const api = require('./index-api');
/**
* 获得资源
*/
const getResourceAsync = (resourceCode) => {
return api.getResourceAsync(resourceCode)
.then(result => {
if (result.code === 200) {
return result.data;
} else {
return {};
}
})
.catch(() => {
return {};
});
};
module.exports = {
getResourceAsync
};
... ...
/**
* passport.js 支付宝登录插件
*
* @author JiangFeng<jeff.jiang@yoho.cn>
* @date 2016/06/21
*/
'use strict';
const util = require('util');
const _ = require('lodash');
const md5 = require('md5');
const passport = require('passport-strategy');
// 支付宝网关地址
const ALIPAY_URL = 'https://mapi.alipay.com/gateway.do';
const defaultOptions = {
service: 'alipay.auth.authorize',
_input_charset: 'utf-8',
sign_type: 'MD5',
target_service: 'user.auth.quick.login'
};
/**
* 将参数排序,拼接成 "参数=参数值" 的格式
*
* @param {Object} params
*/
function paramsToRaw(params) {
let keys = Object.keys(params);
keys = keys.sort();
let string = '';
keys.forEach((key) => {
string += '&' + key + '=' + params[key];
});
string = string.substr(1);
return string;
}
function AlipayStrategy(options, verify) {
if (typeof options === 'function') {
verify = options;
}
passport.Strategy.call(this);
this.name = 'alipay';
this._verify = verify;
}
util.inherits(AlipayStrategy, passport.Strategy);
AlipayStrategy.prototype.authenticate = function(req, options) {
if (req.query && req.query.is_success && req.query.sign && req.query.sign_type) {
let query = req.query;
let sign = query.sign;
let signType = query.sign_type;
delete query.sign_type;
delete query.sign;
let signString = paramsToRaw(query) + options.key;
if (signType === 'MD5' && sign !== md5(signString)) {
this.error('alipay callback sign check fail');
this.fail('alipay callback sign check fail');
return;
}
if (req.query.is_success === 'T') {
let user = {
userId: req.query.user_id,
realName: req.query.realName,
email: req.query.email
};
this.success(user, null);
} else {
this.error('alipay login fail');
this.fail(req.error_code);
}
} else {
let params = _.assign(defaultOptions, options);
let signType = params.sign_type;
delete params.sign_type;
delete params.sign;
let signString = paramsToRaw(params) + options.key;
if (signType === 'MD5') {
params.sign = md5(signString);
params.sign_type = 'MD5';
}
this.redirect(ALIPAY_URL + '?' + paramsToRaw(params));
}
};
exports = module.exports = AlipayStrategy;
exports.Strategy = AlipayStrategy;
... ...
/**
* Created by TaoHuang on 2016/6/20.
*/
'use strict';
const Promise = require('bluebird');
const co = Promise.coroutine;
const _ = require('lodash');
const helpers = global.yoho.helpers;
const indexService = require('./index-service');
/**
* 获得图片
*/
const getLeftBannerAsync = (resourceCode) => {
const DEFAULT_VALUE = {
img: 'http://img12.static.yhbimg.com/' +
'yhb-img01/2015/12/01/07/020a0b6e7ff908d0c2bc4045b4fef42b9f.png?imageView/2/w/252/h/190',
url: ''
};
return co(function * () {
let resource = yield indexService.getResourceAsync(resourceCode);
if (_.isEmpty(resource)) {
return DEFAULT_VALUE;
}
let value = {};
// 有点问题 // passport model 58
value.img = helpers.image(resource[0].data[0].src, 252, 190);
value.url = resource[0].data[0].url;
return value;
})();
};
/**
* 国家数据
*/
const getCountry = () => {
return [
{
areaCode: '+61',
selected: false,
name: '澳大利亚'
},
{
areaCode: '+82',
selected: false,
name: '韩国'
},
{
areaCode: '+1',
selected: false,
name: '加拿大'
},
{
areaCode: '+60',
selected: false,
name: '马来西亚'
},
{
areaCode: '+1',
selected: false,
name: '美国'
},
{
areaCode: '+81',
selected: false,
name: '日本'
},
{
areaCode: '+65',
selected: false,
name: '新加坡'
},
{
areaCode: '+44',
selected: false,
name: '英国'
},
{
areaCode: '+86',
selected: true,
name: '中国'
},
{
areaCode: '+853',
selected: false,
name: '中国澳门'
},
{
areaCode: '+886',
selected: false,
name: '中国台湾'
},
{
areaCode: '+852',
selected: false,
name: '中国香港'
}
];
};
/**
* 各国手机号规则
*/
const _areaMobileVerify = (phone, area) => {
area = area || '86';
phone = phone.trim();
let verify = {
86: {
name: '中国',
match: /^1[3|4|5|8|7][0-9]{9}$/.test(phone)
},
852: {
name: '中国香港',
match: /^[9|6|5][0-9]{7}$/.test(phone)
},
853: {
name: '中国澳门',
match: /^[0-9]{8}$/.test(phone)
},
886: {
name: '中国台湾',
match: /^[0-9]{10}$/.test(phone)
},
65: {
name: '新加坡',
match: /^[9|8][0-9]{7}$/.test(phone)
},
60: {
name: '马来西亚',
match: /^1[1|2|3|4|6|7|9][0-9]{8}$/.test(phone)
},
1: {
name: '加拿大&美国',
match: /^[0-9]{10}$/.test(phone)
},
82: {
name: '韩国',
match: /^01[0-9]{9}$/.test(phone)
},
44: {
name: '英国',
match: /^7[7|8|9][0-9]{8}$/.test(phone)
},
81: {
name: '日本',
match: /^0[9|8|7][0-9]{9}$/.test(phone)
},
61: {
name: '澳大利亚',
match: /^[0-9]{11}$/.test(phone)
}
};
if (verify[area]) {
return verify[area].match;
} else {
return false;
}
};
/**
* 验证国际手机号是否合法
*/
const isAreaMobile = areaMobile => {
if (!areaMobile) {
return false;
}
let mobile = {
area: '86',
phone: ''
};
let splitMobile = areaMobile.split('-');
if (splitMobile.length === 2) {
mobile.area = splitMobile[0];
mobile.phone = splitMobile[1];
} else {
mobile.phone = splitMobile[0];
}
return _areaMobileVerify(mobile.phone, mobile.area);
};
/**
* 验证邮箱是否合法
*/
const verifyEmail = email => {
if (!email) {
return false;
}
const emailRegExp = /^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/;
return emailRegExp.test(email);
};
/**
* 验证手机是否合法
*/
const verifyMobile = phone => {
if (!phone) {
return false;
}
return /^1[3|4|5|8|7][0-9]{9}$/.test(phone);
};
/**
* 生成带区号的手机号码
*/
const makeAreaMobile = (area, mobile) => {
if (!area || area === '86') {
return mobile;
}
return `${area}-${mobile}`;
};
/**
* 密码是否合法
*/
const isPassword = pwd => {
if (!pwd) {
return false;
}
let pwdRegexp = /^([a-zA-Z0-9\-\+_!@\#$%\^&\*\(\)\:\;\.=\[\]\\\',\?]){6,20}$/;
return pwdRegexp.test(_.trim(pwd));
};
module.exports = {
validator: {
verifyMobile,
isAreaMobile,
verifyEmail,
isPassword
},
makeAreaMobile,
getCountry,
getLeftBannerAsync
};
... ...
/**
* 注册 model
*/
'use strict';
const passportHelper = require('./passport-helper');
const REGISTER_LEFT_BANNER_CODE = 'c479ec90120cae7f96e52922b4917064'; // 注册左边的banner
const api = global.yoho.API;
let getRegData = () => {
return passportHelper.getLeftBannerAsync(REGISTER_LEFT_BANNER_CODE);
};
let sendCodeToMobile = (area, mobile) => {
let params = {
method: 'app.register.sendRegCodeToMobile',
area: area,
mobile: mobile
};
return api.post('', params);
};
let validMobileCode = (area, mobile, code) => {
let params = {
method: 'app.register.validRegCode',
area: area,
mobile: mobile,
code: code
};
return api.post('', params);
};
let regMobile = (area, mobile, password, shoppingKey)=> {
let params = {
method: 'app.passport.register',
area: area,
profile: mobile,
password: password
};
if (shoppingKey) {
params.shopping_key = shoppingKey;
}
return api.post('', params);
};
module.exports = {
getRegData,
sendCodeToMobile,
validMobileCode,
regMobile
};
... ...
/**
* Created by TaoHuang on 2016/6/17.
*/
'use strict';
const _ = require('lodash');
const api = global.yoho.API;
const EMPTY = {};
/**
* 根据手机号获取用户信息
*/
const findByMobileAsync = (area, mobile) => {
return api.get('', {
mobile: mobile,
area: area,
method: 'app.passport.getProfileByMobile'
}).then(result => {
if (!result.code || result.code !== 200 || !result.data || _.isEmpty(result.data)) {
return EMPTY;
}
return result.data;
}).catch(() => {
return EMPTY;
});
};
/**
* 根据邮箱获取用户信息
*/
const findByEmailAsync = (email) => {
return api.get('', {
email: email,
method: 'app.passport.getProfileByEmail'
})
.then(result => {
if (!result.code || result.code !== 200 || !result.data || _.isEmpty(result.data)) {
return EMPTY;
}
return result.data;
})
.catch(() => {
return EMPTY;
});
};
module.exports = {
findByMobileAsync,
findByEmailAsync
};
... ...
/**
* Created by TaoHuang on 2016/6/17.
*/
'use strict';
const api = require('./user-api');
module.exports = api;
... ...
/**
* router of sub app channel
* @author: Bi Kai<kai.bi@yoho.cn>
* @date: 2016/05/09
*/
'use strict';
const express = require('express');
const cRoot = './controllers';
const login = require(cRoot + '/login');
// const captcha = require(cRoot + '/captcha');
const back = require(cRoot + '/back');
const reg = require(cRoot + '/reg');
const router = express.Router(); // eslint-disable-line
// 本地登录
router.get('/login', login.common.beforeLogin, login.local.loginPage);
router.post('/login/auth', login.local.login);
router.get('/logout', login.local.logout);
// 微信登录
router.get('/autosign/wechat', login.common.beforeLogin, login.wechat.login); // 微信登录, 兼容 PHP 的路径
router.get('/login/wechat/callback', login.wechat.callback);
// sina登录
router.get('/autosign/sina', login.common.beforeLogin, login.sina.login);
router.get('/login/sina/callback', login.sina.callback);
// qq登录
router.get('/autosign/qq', login.common.beforeLogin, login.qq.login);
router.get('/login/qq/callback', login.qq.callback);
// alipay登录
router.get('/autosign/alipay', login.common.beforeLogin, login.alipay.login);
router.get('/login/alipay/callback', login.alipay.callback);
router.get('/login/account', login.common.needCaptcha);
/**
* 注册页面路由
*/
router.get('/reg/index', reg.index);
router.post('/reg/checkmobile', reg.checkMobile);
router.post('/reg/piccaptcha', reg.picCaptcha);
router.post('/reg/msgcaptcha', reg.msgCaptcha);
router.post('/reg/sendBindMsg', reg.sendBindMsg);
router.post('/reg/mobileregister', reg.mobileRegister);
router.get('/reg/success', reg.success);
/**
* 找回密码首页信息
*/
// 找回密码首页
router.get('/back/index', back.index);
// 实时验证输入是否正确
router.post('/back/authcode',
// captcha.requiredAPI,
back.validateInputAPI,
back.getUserInfoAPI);
// 提交按钮邮件API
router.post('/back/email',
// captcha.requiredPage,
back.validateUserPage,
back.sendCodePage,
back.saveInSession);
// 提交按钮手机API
router.post('/back/mobile',
// captcha.requiredPage,
back.validateUserPage,
back.sendCodePage,
back.saveInSession);
/**
* 邮件找回密码
*/
// 发送邮件成功页面
router.get('/back/sendEmail',
back.validateEmailInSession,
back.sendEmailPage);
/**
* 短信找回密码
*/
// 验证手机短信页面
router.get('/back/verification',
back.validateMobileInSession,
// captcha.requiredPage,
back.verifyCodeByMobilePage);
// 重新发送短信接口
router.post('/back/sendBackMobile',
// captcha.requiredAPI,
back.validateMobileAPI,
back.sendBackMobileAPI);
// 验证手机验证码接口
router.post('/back/backMobile',
// captcha.requiredAPI,
back.verifyCodeByMobileAPI);
/**
* 重置密码
*/
// 重置密码页面
router.get('/back/backcode',
back.validateExistCodePage,
back.validateCodeByMobilePage,
back.validateCodeByEmailPage,
back.resetPasswordPage);
// 重置密码接口
router.post('/back/update',
back.validateExistCodePage,
back.validateCodeByMobilePage,
back.validatePwdPage,
back.updatePwdAPI);
// 重置密码成功页面
router.get('/back/resetSuccess',
back.validateSuccessStatusPage,
back.resetPwdSuccessPage);
module.exports = router;
... ...
<div class="back-page passport-page yoho-page clearfix">
{{# back}}
{{> back/cover}}
<div class="content">
<div class="back-header clearfix">
<h2 class="title">找回密码</h2>
<span id="country-code" class="country-code">
<em>{{countryName}} +{{countryCode}}</em>
<i class="iconfont">&#xe61d;</i>
</span>
<ul id="country-code-list" class="country-code-list">
{{# countryList}}
<li data-cc="{{areaCode}}">{{name}} {{areaCode}}</li>
{{/ countryList}}
</ul>
</div>
<form id="back-form" class="back-form" action="/passport/back/email" method="post">
<input id="country-code-hide" type="hidden" name="area" value="+86">
<ul>
<li class="input-container-li clearfix">
<input id="phone-num" class="input va phone-num" type="text" name="phoneNum" placeholder="邮箱/手机号码" autocomplete="off">
<span id="account-err" class="err-tip hide">
<i></i>
<em>账户名不能为空</em>
</span>
</li>
<li class="input-container-li clearfix">
<input id="captcha" class="input va captcha" type="text" name="verifyCode" placeholder="验证码" autocomplete="off" maxlength="4">
<img id="captcha-img" class="captcha-img" src="{{captchaUrl}}" alt="">
<a id="change-captcha" class="link change-captcha">换一张</a>
<span id="captcha-err" class="err-tip captcha-err hide">
<i></i>
<em>验证码不能为空</em>
</span>
</li>
<li class="input-container-li clearfix">
<input name="refer" id="refer" type="hidden" value="http%3A%2F%2Fwww.yohobuy.com%2F">
<input id="find-btn" class="btn find-btn disable" type="submit" value="下一步" disabled="">
</li>
</ul>
</form>
</div>
{{/ back}}
</div>
... ...
<div class="reset-pwd-page back-page passport-page yoho-page clearfix">
{{# resetPwd}}
{{> back/cover}}
<div class="content">
<h2 class="title2">重置密码</h2>
<form id="reset-pwd-form" class="reset-pwd-form" method="POST" action="/passport/back/update">
<ul>
<li class="input-container-li po-re">
<input id="pwd" class="input va pwd" type="password" name="pwd" placeholder="新密码"
maxlength="20">
<div class="pwd-intensity-container">
<span class="pwd-intensity low"></span>
<span class="pwd-intensity mid"></span>
<span class="pwd-intensity high"></span>
</div>
<div id="pwd-tips" class="pwd-tips hide">
<div class="default" id="pwd-tip1">
<i></i>
密码只支持6-20位字符
</div>
<div class="default" id="pwd-tip2">
<i></i>
由字母、 数字组合,不能包含特殊符号
</div>
</div>
<span id="pwd-err" class="err-tip hide">
<i></i>
<em>请输入密码</em>
</span>
</li>
<li class="input-container-li clearfix po-re">
<input id="re-input" class="input va re-input repwd" type="password" name="re-input"
placeholder="再次输入" maxlength="20">
<span id="repwd-err" class="err-tip hide">
<i></i>
<em>请输入密码确认</em>
</span>
</li>
<li class="input-container-li clearfix">
<input type="hidden" name="code" value="{{code}}">
<input type="hidden" name="mobile" value="{{mobile}}">
<input type="hidden" name="area" value="{{area}}">
<input type="hidden" name="token" value="{{token}}">
<input type="hidden" name="createdAt" value="{{createdAt}}">
<input id="reset-pwd-btn" class="btn reset-pwd-btn" type="submit" value="提交" disabled="">
</li>
</ul>
</form>
</div>
{{/ resetPwd}}
</div>
... ...
<div class="reset-success-page back-page passport-page yoho-page clearfix">
{{# resetSuccess}}
{{> back/cover}}
<div class="content">
<div class="success-text">
<i class="iconfont">&#xe620;</i><span>恭喜!</span>密码修改成功,&nbsp;<span id="count-down">5</span>&nbsp;&nbsp;秒后将跳转至首页
</div>
<a class="success-btn" href="/">随便逛逛</a>
</div>
{{/ resetSuccess}}
</div>
<script type="text/javascript">
(function() {
var count = 5,
countDown = document.getElementById('count-down');
var timer = setInterval(function(){
if (count > 1) {
count--;
countDown.innerHTML = count;
} else {
location.href = '/';
}
}, 1000);
})();
</script>
... ...
<div class="send-email-page passport-page yoho-page clearfix">
{{# sendEmail}}
{{> back/cover}}
<div class="content">
<div class="send-tips"><i class="iconfont">&#xe61e;</i>我们已经把验证邮件发送至您的邮箱,请在24小时内通过邮件内的<br>链接继续设置新的密码。</div>
<div class="no-find">没有收到?到您邮箱的垃圾邮件里找找。</div>
<div class="to-my-email">
<a href="{{email}}" target="_blank" class="btn_b_ar_r">去我的邮箱&gt;</a>
</div>
</div>
{{/ sendEmail}}
</div>
... ...
<div class="verification-page back-page passport-page yoho-page clearfix">
{{# verification}}
{{> back/cover}}
<div class="content">
<form id="verification-form" class="verification-form" method="POST" action="/passport/back/backmobile">
<ul>
<li class="head-title">验证身份</li>
<li class="po-re">
<label class="pn-label">手机号码</label>
<span class="country-code">+{{area}}</span>
<span class="phone-num">{{mobile}}</span>
</li>
<li class="po-re">
<input id="captcha" class="input va captcha" type="text" name="code" maxlength="4">
<input id="send-captcha" class="btn send-captcha" type="button" value="发送验证码" disabled="">
<div id="captcha-tip" class="captcha-tips"><i class="iconfont">&#xe61f;</i>验证码已发送至您的手机,请查收</div>
<span id="err-tip" class="err-tip hide">
<i></i>
<em>请输入验证码</em>
</span>
</li>
<li>
<input name="area" id="area" type="hidden" value="{{area}}">
<input name="mobile" id="mobile" type="hidden" value="{{mobile}}">
<input name="verifyCode" id="captchaPic" type="hidden" value="{{verifyCode}}">
<input name="refer" id="refer" type="hidden" value="">
<a id="next-step" class="btn next-step disable" href="javascript:;">下一步</a>
<!-- <input id="next-step" class="btn next-step disable" type="submit" value="下一步" disabled=""> -->
</li>
</ul>
</form>
</div>
{{/ verification}}
</div>
... ...
<div class="login-page passport-page yoho-page clearfix">
{{# passport}}
{{> login/cover}}
<div class="content">
<ul class="login-ul">
<li class="relative clearfix">
<h2 class="title">会员登录</h2>
<span id="country-code" class="country-code right">
<em>{{countryName}} {{countryCode}}</em>
<i class="iconfont">&#xe600;</i>
<ul id="country-list" class="country-list">
{{#each countryList}}
<li data-cc="{{areaCode}}" {{# selected}}selected{{/selected}}>{{name}} {{areaCode}}</li>
{{/each}}
</ul>
</span>
</li>
<li class="relative">
<input id="account" class="account input va" name="account" value="{{bindMobile}}" type="text" placeholder="邮箱/手机号码" autocomplete="off">
<span class="err-tip hide">
<i></i>
<em></em>
</span>
</li>
<li class="relative">
<input id="password" class="password input va" name="password" type="password" placeholder="密码" autocomplete="off" maxlength="20">
<span id="caps-lock" class="caps-lock hide">大写状态开启</span>
<span class="err-tip hide">
<i></i>
<em>请输入密码</em>
</span>
</li>
<li class="clearfix captcha-wrap hide">
<input id="captcha" class="input va captcha" type="text" name="captcha" placeholder="图形验证码" autocomplete="off" maxlength="4">
<img id="captcha-img" class="captcha-img" alt="">
<a class="link change-captcha">换一张</a>
<span class="err-tip hide">
<i></i>
<em></em>
</span>
</li>
<li>
<span id="login-btn" class="login-btn btn">登录</span>
</li>
<li class="other-opts">
<span class="remember-me">
<i class="iconfont">&#xe613;</i>
记住登录状态
</span>
<span class="right">
<a class="forget-password" href="{{forgetPwd}}">忘记密码?</a>
|
<a class="fast-reg" href="{{fastReg}}">快速注册</a>
</span>
</li>
<li class="third-party-login">
<a href="{{weixinLogin}}">
<span class="icon weixin"></span>
</a>
<a href="{{qqLogin}}">
<span class="icon qq"></span>
</a>
<a href="{{weiboLogin}}">
<span class="icon weibo"></span>
</a>
<a href="{{alipayLogin}}">
<span class="icon alipay"></span>
</a>
<a href="{{doubanLogin}}">
<span class="icon douban"></span>
</a>
<a href="{{renrenLogin}}">
<span class="icon renren"></span>
</a>
</li>
</ul>
<input id="country-code-hide" name="countryCode" type="hidden" value="{{countryCode}}">
</div>
{{/ passport}}
</div>
... ...
{{> sign-header}}
<div class="passport-page yoho-page clearfix">
{{# passport}}
<div class="center-content">
{{> reg/register}}
</div>
{{/ passport}}
</div>
... ...
<div class="passport-page yoho-page clearfix">
{{# passport}}
{{> reg/cover}}
<div class="content">
<div class="register-page">
<div class="success-box">
<div class="success-text">
<span>恭喜!</span>账号注册成功,&nbsp;<span id="count-down">5</span>&nbsp;&nbsp;秒后将跳转至首页
</div>
<a class="success-btn" href="{{goShoppong}}" data-url="{{goUrl}}">随便逛逛</a>
</div>
</div>
</div>
{{/ passport}}
</div>
... ...
{{> layout/header}}
<div class="passport-page yoho-page clearfix">
{{# passport}}
{{> passport/cover}}
<div class="content">
{{> passport/register}}
</div>
{{/ passport}}
<div class="page-tip clearfix">为了给您更好的购物体验, 建议您创建YOHO!Family账号</div>
</div>
{{> layout/footer}}
\ No newline at end of file
... ...
<div class="passport-cover">
<div class="cover-content">
{{#if coverHref}}
<a href="{{coverHref}}" target="_bank">
<img class="cover-img" src="{{coverImg}}">
</a>
{{^}}
<img class="cover-img" src="{{coverImg}}">
{{/if}}
</div>
</div>
... ...
<div class="passport-cover">
<div class="cover-content">
{{#if coverHref}}
<a href="{{coverHref}}" target="_bank">
<img class="cover-img" src="{{coverImg}}">
</a>
{{^}}
<img class="cover-img" src="{{coverImg}}">
{{/if}}
</div>
</div>
\ No newline at end of file
... ...
<div class="register-page">
<ul>
<li class="clearfix">
<select id="region" class="region" name="region">
{{#each region}}
<option {{#if selected}}selected="selected"{{/if}} value="{{areaCode}}">{{name}}</option>
{{/each}}
</select>
</li>
<li class="clearfix" data-index="0">
<span id="country-code" class="country-code">{{location}}</span>
<input value="" id="phone-num" class="input va phone-num" type="text" name="phoneNum" placeholder="请输入手机号码" autocomplete="off">
</li>
<li class="w330 clearfix" data-index="1">
<input id="captcha" class="input va captcha" type="text" name="captcha" placeholder="图形验证码" autocomplete="off" maxlength="4">
<img id="captcha-img" class="captcha-img" src="{{captchaUrl}}" alt="">
<a class="link change-captcha">换一张</a>
</li>
<li class="clearfix" data-index="2">
<input id="msg-captcha" class="input va msg-captcha" type="text" name="msgCaptcha" placeholder="短信验证码" autocomplete="off" maxlength="4">
<input id="send-captcha" class="btn send-captcha disable" type="button" value="获取短信验证码">
<span id="msg-tip" class="hide msg-tip">短信验证码已发送至您的手机,请查收</span>
</li>
<li class="clearfix" data-index="3">
<input id="pwd" class="input va pwd" name="pwd" placeholder="设置密码" autocomplete="off" maxlength="20" type="password">
<div class="pwd-intensity-container">
<span class="pwd-intensity low"></span>
<span class="pwd-intensity mid"></span>
<span class="pwd-intensity high"></span>
</div>
<div id="pwd-tips" class="hide pwd-tips">
<div class="default" id="pwd-tip1"><i></i>密码只支持6-20位字符</div>
<div class="default" id="pwd-tip2"><i></i>由字母、 数字组合,不能包含特殊符号</div>
</div>
</li>
<li class="items-container clearfix">
<input id="agree-terms" class="agree-terms" type="checkbox" checked="">
<span>
我已阅读并同意遵守
<a class="link go-yoho-items" href="{{itemUrl}}" target="_blank">YOHO!BUY 有货服务条款</a>
</span>
</li>
<li class="clearfix">
<input name="refer" id="refer" type="hidden" value="{{referUrl}}">
<input id="register-btn" class="btn register-btn disable" type="submit" value="{{regBtnText}}" disabled="">
</li>
{{# loginUrl}}
<li class="quick-login-container">
我已注册YOHO!BUY 有货账号
<a class="link go-login" href="{{.}}">快速登录</a>
</li>
{{/loginUrl}}
{{# skipUrl}}
<li class="skip-user-info">
<a href="{{.}}">跳过此步</a>
</li>
{{/skipUrl}}
</ul>
<div id="err-tip" class="err-tip hide">
<span></span>
<b></b>
</div>
<input name="" type="hidden" id="open-id" value="{{openId}}"/>
<input name="" type="hidden" id="source-type" value="{{sourceType}}"/>
</div>
... ...
<div class="passport-cover">
<div class="cover-content">
{{#if coverHref}}
<a href="{{coverHref}}" target="_bank">
<img class="cover-img" src="{{coverImg}}">
</a>
{{^}}
<img class="cover-img" src="{{coverImg}}">
{{/if}}
</div>
</div>
\ No newline at end of file
... ...
<div class="register-page">
<ul>
<li class="w330 clearfix">
<div class="title">
JOIN YOHO!BLK
</div>
</li>
<li class="w330 clearfix" data-index="0">
<select id="region" class="region" name="region">
{{#each region}}
<option {{#if selected}}selected="selected"{{/if}} value="{{areaCode}}">{{name}}</option>
{{/each}}
</select>
<div id="phone" class="phone" >
<span id="country-code" class="country-code">{{location}}</span>
<input value="" id="phone-num" class="input va phone-num" type="text" name="phoneNum"
placeholder="Phone Number" autocomplete="off">
</div>
</li>
<li class="w330 clearfix" data-index="1">
<input id="captcha" class="input va captcha" type="text" name="captcha" placeholder="图形验证码"
autocomplete="off" maxlength="4">
<div>
<img id="captcha-img" class="captcha-img" src="{{captchaUrl}}" alt="">
<a class="link change-captcha"><img src="" alt=""></a>
</div>
</li>
<li class="clearfix" data-index="2">
<input id="msg-captcha" class="input va msg-captcha" type="text" name="msgCaptcha" placeholder="短信验证码"
autocomplete="off" maxlength="4">
<input id="send-captcha" class="btn send-captcha disable" type="button" value="获取短信验证码">
<span id="msg-tip" class="hide msg-tip">短信验证码已发送至您的手机,请查收</span>
</li>
<li class="clearfix" data-index="3">
<div class="pwd-intensity-container">
<span class="pwd-intensity low"></span>
<span class="pwd-intensity mid"></span>
<span class="pwd-intensity high"></span>
</div>
<input id="pwd" class="input va pwd" name="pwd" placeholder="Password" autocomplete="off" maxlength="20"
type="password">
<div id="pwd-tips" class="hide pwd-tips">
<div class="default" id="pwd-tip1"><i></i>密码只支持6-20位字符</div>
<div class="default" id="pwd-tip2"><i></i>由字母、 数字组合,不能包含特殊符号</div>
</div>
</li>
<li class="items-container clearfix">
<input id="agree-terms" class="agree-terms" type="checkbox" checked="">
<span>
我已阅读并同意遵守
<a class="link go-yoho-items" href="{{itemUrl}}" target="_blank">YOHO!BUY 有货服务条款</a>
</span>
</li>
<li class="clearfix">
<input name="refer" id="refer" type="hidden" value="{{referUrl}}">
<input id="register-btn" class="btn register-btn disable" type="submit" value="{{regBtnText}}" disabled="">
</li>
{{# loginUrl}}
<li class="quick-login-container">
已注册YOHO!BLK账号
<a class="link go-login" href="{{.}}">快速登录</a>
</li>
{{/loginUrl}}
{{# skipUrl}}
<li class="skip-user-info">
<a href="{{.}}">跳过此步</a>
</li>
{{/skipUrl}}
</ul>
<div id="err-tip" class="err-tip hide">
<span></span>
<b></b>
</div>
<input name="" type="hidden" id="open-id" value="{{openId}}"/>
<input name="" type="hidden" id="source-type" value="{{sourceType}}"/>
</div>
... ...
... ... @@ -10,4 +10,5 @@ module.exports = app => {
app.use('/partial', require('./apps/partial')); // 组件demo
// 业务模块
//app.use('/passport', require('./apps/passport'));
};
... ...
# 频道页数据
```
{
content: [
//轮播banner
{
slider: [
{
img: '',
link: ''
},
...
]
},
//品牌广告
{
brandsAd: [
{
img: '',
name: '',
des: '',
link: ''
},
..
]
},
//新品抢鲜看
{
floorZh: '新品抢鲜看',
floorEn: 'NEW ARRIVALS',
newArrivals: [
{
img: '',
name: '',
link: ''
},
...
]
},
//经典品牌
{
floorZh: '经典品牌',
floorEn: 'CLASSIC BRANDS',
classicBrands: [
{
big: [
{
img: '',
link: ''
},
...
],
small: [
{
img: '',
link: ''
},
...
]
},
...
]
},
//潮流标志
{
floorZh: '潮流标志',
floorEn: 'STYLE ICON',
styleIcon: [
{
img: '',
name: '',
des: '',
link: ''
},
...
]
},
//广告位banner
{
adBanner: {
img: '',
link: ''
}
},
//资讯
{
floorZh: '资讯',
floorEn: 'EDITORIAL',
editorial: [
big: {
img: '',
link: ''
},
small: [
{
img: '',
link: ''
},
...
]
]
}
]
}
```
\ No newline at end of file
... ...
... ... @@ -32,8 +32,8 @@ exports.createPagination = function(pagination, options) {
n, // page number ?page=n
queryParams = '', // paginate with query parameter
page = parseInt(pagination.page, 10), // current page number
leftText = '<i class="iconfont">&#xe60e;</i>', // prev
rightText = '<i class="iconfont">&#xe643;</i>', // next
leftText = '<i class="iconfont">&#xe607;</i>', // prev
rightText = '<i class="iconfont">&#xe606;</i>', // next
paginationClass = 'blk-pagination'; // pagination <ul> default class
var pageCount,
... ...
### 测试数据
- 使用目的
为了快速构建组件和业务模型,模拟服务端的接口返回,首先集中开发前端。
- 如何使用
全局安装json-server
npm i -g json-server
启动
json-server --watch mock/xx.json
xx.json就是需要模拟服务端返回的json数据
\ No newline at end of file
... ...
{
"areas": [
{"id": 0, "data": [
{"value":"11","text":"\u5317\u4eac\u5e02","is_support":"Y"},
{"value":"12","text":"\u5929\u6d25\u5e02","is_support":"Y"},
{"value":"13","text":"\u6cb3\u5317\u7701","is_support":"Y"},
{"value":"14","text":"\u5c71\u897f\u7701","is_support":"Y"},
{"value":"15","text":"\u5185\u8499\u53e4\u81ea\u6cbb\u533a","is_support":"Y"},
{"value":"21","text":"\u8fbd\u5b81\u7701","is_support":"Y"},
{"value":"22","text":"\u5409\u6797\u7701","is_support":"Y"},
{"value":"23","text":"\u9ed1\u9f99\u6c5f\u7701","is_support":"Y"},
{"value":"31","text":"\u4e0a\u6d77\u5e02","is_support":"Y"},
{"value":"32","text":"\u6c5f\u82cf\u7701","is_support":"N"},
{"value":"33","text":"\u6d59\u6c5f\u7701","is_support":"Y"},
{"value":"34","text":"\u5b89\u5fbd\u7701","is_support":"Y"},
{"value":"35","text":"\u798f\u5efa\u7701","is_support":"N"},
{"value":"36","text":"\u6c5f\u897f\u7701","is_support":"N"},
{"value":"37","text":"\u5c71\u4e1c\u7701","is_support":"N"},
{"value":"41","text":"\u6cb3\u5357\u7701","is_support":"N"},
{"value":"42","text":"\u6e56\u5317\u7701","is_support":"N"},
{"value":"43","text":"\u6e56\u5357\u7701","is_support":"Y"},
{"value":"44","text":"\u5e7f\u4e1c\u7701","is_support":"Y"},
{"value":"45","text":"\u5e7f\u897f\u58ee\u65cf\u81ea\u6cbb\u533a","is_support":"Y"},
{"value":"46","text":"\u6d77\u5357\u7701","is_support":"Y"},
{"value":"50","text":"\u91cd\u5e86\u5e02","is_support":"Y"},
{"value":"51","text":"\u56db\u5ddd\u7701","is_support":"Y"},
{"value":"52","text":"\u8d35\u5dde\u7701","is_support":"Y"},
{"value":"53","text":"\u4e91\u5357\u7701","is_support":"N"},
{"value":"54","text":"\u897f\u85cf\u81ea\u6cbb\u533a","is_support":"Y"},
{"value":"61","text":"\u9655\u897f\u7701","is_support":"Y"},
{"value":"62","text":"\u7518\u8083\u7701","is_support":"Y"},
{"value":"63","text":"\u9752\u6d77\u7701","is_support":"Y"},
{"value":"64","text":"\u5b81\u590f\u56de\u65cf\u81ea\u6cbb\u533a","is_support":"Y"},
{"value":"65","text":"\u65b0\u7586\u7ef4\u543e\u5c14\u81ea\u6cbb\u533a","is_support":"N"}]
},{
"id": 32, "data": [
{"value":"3201","text":"\u5357\u4eac\u5e02","is_support":"Y"},
{"value":"3202","text":"\u65e0\u9521\u5e02","is_support":"N"},
{"value":"3203","text":"\u5f90\u5dde\u5e02","is_support":"N"},
{"value":"3204","text":"\u5e38\u5dde\u5e02","is_support":"N"},
{"value":"3205","text":"\u82cf\u5dde\u5e02","is_support":"N"},
{"value":"3206","text":"\u5357\u901a\u5e02","is_support":"N"},
{"value":"3207","text":"\u8fde\u4e91\u6e2f\u5e02","is_support":"N"},
{"value":"3208","text":"\u6dee\u5b89\u5e02","is_support":"N"},
{"value":"3209","text":"\u76d0\u57ce\u5e02","is_support":"N"},
{"value":"3210","text":"\u626c\u5dde\u5e02","is_support":"N"},
{"value":"3211","text":"\u9547\u6c5f\u5e02","is_support":"N"},
{"value":"3212","text":"\u6cf0\u5dde\u5e02","is_support":"N"},
{"value":"3213","text":"\u5bbf\u8fc1\u5e02","is_support":"N"}]
}, {
"id": 3201, "data": [{"value":"320102","text":"\u7384\u6b66\u533a","is_support":"Y"},
{"value":"320103","text":"\u767d\u4e0b\u533a","is_support":"Y"},
{"value":"320104","text":"\u79e6\u6dee\u533a","is_support":"Y"},
{"value":"320105","text":"\u5efa\u90ba\u533a","is_support":"Y"},
{"value":"320106","text":"\u9f13\u697c\u533a","is_support":"Y"},
{"value":"320107","text":"\u4e0b\u5173\u533a","is_support":"Y"},
{"value":"320111","text":"\u6d66\u53e3\u533a","is_support":"Y"},
{"value":"320113","text":"\u6816\u971e\u533a","is_support":"Y"},
{"value":"320114","text":"\u96e8\u82b1\u53f0\u533a","is_support":"Y"},
{"value":"320115","text":"\u6c5f\u5b81\u533a","is_support":"Y"},
{"value":"320116","text":"\u516d\u5408\u533a","is_support":"Y"},
{"value":"320124","text":"\u6ea7\u6c34\u53bf","is_support":"Y"},
{"value":"320125","text":"\u9ad8\u6df3\u53bf","is_support":"Y"}]
}
]
}
\ No newline at end of file
... ...
... ... @@ -93,6 +93,7 @@
"yoho-handlebars": "^4.0.5",
"yoho-jquery": "^1.12.4",
"yoho-jquery-lazyload": "^1.9.7",
"yoho-jquery-placeholder": "^2.3.1",
"yoho-slider": "0.0.2"
}
}
... ...
No preview for this file type
... ... @@ -2,7 +2,7 @@
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
Created by FontForge 20120731 at Sat Jul 2 17:15:07 2016
Created by FontForge 20120731 at Sun Jul 3 15:46:38 2016
By admin
</metadata>
<defs>
... ... @@ -19,7 +19,7 @@ Created by FontForge 20120731 at Sat Jul 2 17:15:07 2016
bbox="0 -212 1303 896.303"
underline-thickness="50"
underline-position="-100"
unicode-range="U+0078-F0066"
unicode-range="U+0078-E61A"
/>
<missing-glyph horiz-adv-x="374"
d="M34 0v682h272v-682h-272zM68 34h204v614h-204v-614z" />
... ... @@ -33,78 +33,78 @@ d="M34 0v682h272v-682h-272zM68 34h204v614h-204v-614z" />
d="M281 543q-27 -1 -53 -1h-83q-18 0 -36.5 -6t-32.5 -18.5t-23 -32t-9 -45.5v-76h912v41q0 16 -0.5 30t-0.5 18q0 13 -5 29t-17 29.5t-31.5 22.5t-49.5 9h-133v-97h-438v97zM955 310v-52q0 -23 0.5 -52t0.5 -58t-10.5 -47.5t-26 -30t-33 -16t-31.5 -4.5q-14 -1 -29.5 -0.5
t-29.5 0.5h-32l-45 128h-439l-44 -128h-29h-34q-20 0 -45 1q-25 0 -41 9.5t-25.5 23t-13.5 29.5t-4 30v167h911zM163 247q-12 0 -21 -8.5t-9 -21.5t9 -21.5t21 -8.5q13 0 22 8.5t9 21.5t-9 21.5t-22 8.5zM316 123q-8 -26 -14 -48q-5 -19 -10.5 -37t-7.5 -25t-3 -15t1 -14.5
t9.5 -10.5t21.5 -4h37h67h81h80h64h36q23 0 34 12t2 38q-5 13 -9.5 30.5t-9.5 34.5q-5 19 -11 39h-368zM336 498v228q0 11 2.5 23t10 21.5t20.5 15.5t34 6h188q31 0 51.5 -14.5t20.5 -52.5v-227h-327z" />
<glyph glyph-name="uniE600" unicode="&#xe600;"
d="M900 660h-776q-8 0 -14 -6t-6 -14v-21q0 -8 6 -14t14 -6h776q8 0 14 6t6 14v21q0 8 -6 14t-14 6zM900 415h-776q-8 0 -14 -6t-6 -15v-20q0 -9 6 -15t14 -6h776q8 0 14 6t6 15v20q0 9 -6 15t-14 6zM900 169h-776q-8 0 -14 -6t-6 -14v-21q0 -8 6 -14t14 -6h776q8 0 14 6
t6 14v21q0 8 -6 14t-14 6z" />
<glyph glyph-name="uniE601" unicode="&#xe601;"
d="M512 -30q-112 0 -207.5 55.5t-151 151t-55.5 207.5t55.5 207.5t151 151t207.5 55.5t207.5 -55.5t151 -151t55.5 -207.5t-55.5 -207.5t-151 -151t-207.5 -55.5zM512 734q-95 0 -175.5 -47t-127.5 -127.5t-47 -175.5t47 -175.5t127.5 -127.5t175.5 -47t175.5 47
t127.5 127.5t47 175.5t-47 175.5t-127.5 127.5t-175.5 47zM633 259l-184 59v280h64v-233l140 -45z" />
d="M0 896h1024v-1024h-1024v1024zM85 -43h854v854h-854v-854z" />
<glyph glyph-name="uniE602" unicode="&#xe602;"
d="M1024 896h-1024v-1024h1024v1024zM448 101l-237 238l90 90l147 -146l307 306l90 -90l-397 -397v-1z" />
<glyph glyph-name="uniE603" unicode="&#xe603;"
d="M512 -128q-139 0 -257 68.5t-186.5 186.5t-68.5 257t68.5 257t186.5 186.5t257 68.5t257 -68.5t186.5 -186.5t68.5 -257t-68.5 -257t-186.5 -186.5t-257 -68.5zM512 -14q108 0 200 53t145 145t53 200t-53 200t-145 145t-200 53t-200 -53t-145 -145t-53 -200t53 -200
t145 -145t200 -53zM512 100q118 0 201 83t83 201t-83 201t-201 83t-201 -83t-83 -201t83 -201t201 -83z" />
<glyph glyph-name="uniE604" unicode="&#xe604;"
d="M512 684q159 0 271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5zM512 812q-139 0 -257 -68.5t-186.5 -186.5t-68.5 -257t68.5 -257t186.5 -186.5t257 -68.5t257 68.5t186.5 186.5t68.5 257t-68.5 257
t-186.5 186.5t-257 68.5z" />
<glyph glyph-name="uniE605" unicode="&#xe605;"
d="M968 -37l-163 164q69 108 69 234q0 119 -57.5 219t-157 158.5t-216 58.5t-216 -58.5t-157 -158.5t-57.5 -218.5t57.5 -219t157 -158.5t216.5 -58q124 0 231 69l163 -164q27 -28 65 -28t65 27.5t27 66t-27 66.5zM443.5 50q-127.5 0 -217.5 91.5t-90 220t90 219.5t217.5 91
t217.5 -91t90 -219.5t-90 -220t-217.5 -91.5z" />
<glyph glyph-name="uniE606" unicode="&#xe606;"
d="M387 158l45 -46l272 272l-272 272l-45 -46l226 -226z" />
<glyph glyph-name="uniE607" unicode="&#xe607;"
d="M704 665l-41 39l-343 -320l343 -320l41 39l-301 281z" />
<glyph glyph-name="uniE608" unicode="&#xe608;"
d="M972 373q-6 37 -32.5 66t-67.5 42q-36 11 -119 31q36 51 55 105t19 96t-13 76q-13 30 -36 46t-53 16q-22 0 -39.5 -7.5t-28 -17t-20 -27.5t-13 -28.5t-9.5 -29.5q-5 -14 -7 -22q-11 -32 -60.5 -80t-119.5 -87q-73 -41 -147 -100q-16 -12 -16 -32v-400q0 -21 17 -34
q42 -30 165 -53.5t209 -23.5q62 0 88 15q58 32 156 305q40 30 58.5 67.5t13.5 76.5v0zM122 492q-28 0 -47.5 -20t-19.5 -47v-409q0 -27 19.5 -47t47.5 -20t47.5 20t19.5 47v409q0 27 -19.5 47t-47.5 20v0zM122 492z" />
d="M959 -62l-85 -85l-362 362l-362 -362l-85 85l362 362l-362 362l85 85l362 -362l362 362l85 -85l-362 -362z" />
<glyph glyph-name="uniE609" unicode="&#xe609;"
d="M774 201h-524l262 366z" />
<glyph glyph-name="uniE60A" unicode="&#xe60a;"
d="M512 188l-280 392h560z" />
<glyph glyph-name="uniE60B" unicode="&#xe60b;"
d="M838 185l40 39l-366 366l-366 -366l40 -39l326 326z" />
d="M903 577l-68 69l-388 -388l-231 230l-68 -68l299 -298l65 65z" />
<glyph glyph-name="uniE60C" unicode="&#xe60c;"
d="M493 487h69v-259h-69v259zM527.5 179q-17.5 0 -30 -12.5t-12.5 -30t12.5 -30t30 -12.5t30 12.5t12.5 30t-12.5 30t-30 12.5zM626 573q-27 11 -57 16t-68.5 3.5t-83 -19.5t-88.5 -52q-104 -83 -100 -225q13 -210 159 -272q72 -31 144 -30q39 0 90 14.5t94 47.5
q46 37 75 97.5t29 140.5q0 63 -17.5 113.5t-48 82.5t-61 51t-67.5 32zM518 58q-158 8 -211 144q-50 171 64 269q81 70 180 60q56 -10 97.5 -34.5t64.5 -56.5t34 -65t11 -67q0 -19 -1 -36.5t-6 -45t-14.5 -49.5t-27.5 -46t-42.5 -40t-63 -25.5t-85.5 -7.5z" />
<glyph glyph-name="uniE60D" unicode="&#xe60d;"
d="M512 641l-392 -682h784zM512 583l341 -595h-682zM512 119zM512 77q-14 0 -23.5 9.5t-9.5 23t9.5 23t23.5 9.5t23.5 -9.5t9.5 -23t-9.5 -23t-23.5 -9.5zM512 175q-7 0 -11.5 9.5t-4.5 23.5l-17 148q0 13 9.5 22.5t23.5 9.5t23.5 -9.5t9.5 -22.5l-17 -148q0 -14 -4.5 -23.5
t-11.5 -9.5zM512 175z" />
<glyph glyph-name="uniE60E" unicode="&#xe60e;"
d="M704 665l-41 39l-343 -320l343 -320l41 39l-301 281z" />
d="M972 373q-6 37 -32.5 66t-67.5 42q-36 11 -119 31q36 51 55 105t19 96t-13 76q-13 30 -36 46t-53 16q-22 0 -39.5 -7.5t-28 -17t-20 -27.5t-13 -28.5t-9.5 -29.5q-5 -14 -7 -22q-11 -32 -60.5 -80t-119.5 -87q-73 -41 -147 -100q-16 -12 -16 -32v-400q0 -21 17 -34
q42 -30 165 -53.5t209 -23.5q62 0 88 15q58 32 156 305q40 30 58.5 67.5t13.5 76.5v0zM122 492q-28 0 -47.5 -20t-19.5 -47v-409q0 -27 19.5 -47t47.5 -20t47.5 20t19.5 47v409q0 27 -19.5 47t-47.5 20v0zM122 492z" />
<glyph glyph-name="uniE60F" unicode="&#xe60f;"
d="M498 765q-105 0 -193.5 -51t-139.5 -139.5t-51 -193t51 -193t139.5 -140t193 -51.5t193 51.5t140 140t51.5 193t-51.5 193t-140 139.5t-192.5 51zM498 45q-92 0 -169 45t-122 122.5t-45 169t45 168.5t122 122t168.5 45t169 -45t122.5 -122t45 -168.5t-45 -169
t-122.5 -122.5t-168.5 -45zM498 45zM463 198q0 -15 10 -25t24.5 -10t25 10t10.5 24.5t-10.5 25t-25 10.5t-24.5 -10.5t-10 -24.5zM463 198zM498 605q-49 0 -83 -33.5t-34 -81.5q0 -10 7 -17t16.5 -7t16.5 7t7 17q0 29 20 49t50 20q29 0 49.5 -21t20.5 -50q0 -15 -39 -54
q-17 -17 -26.5 -27.5t-19 -29t-9.5 -36.5v-39q0 -10 7 -17t16.5 -7t16.5 7t7 17v39q0 19 41 60q17 17 26 27t17.5 27t8.5 33q0 49 -34 83t-82 34zM498 605z" />
<glyph glyph-name="uniE611" unicode="&#xe611;"
d="M900 660h-776q-8 0 -14 -6t-6 -14v-21q0 -8 6 -14t14 -6h776q8 0 14 6t6 14v21q0 8 -6 14t-14 6zM900 415h-776q-8 0 -14 -6t-6 -15v-20q0 -9 6 -15t14 -6h776q8 0 14 6t6 15v20q0 9 -6 15t-14 6zM900 169h-776q-8 0 -14 -6t-6 -14v-21q0 -8 6 -14t14 -6h776q8 0 14 6
t6 14v21q0 8 -6 14t-14 6z" />
<glyph glyph-name="uniE617" unicode="&#xe617;"
d="M968 -37l-163 164q69 108 69 234q0 119 -57.5 219t-157 158.5t-216 58.5t-216 -58.5t-157 -158.5t-57.5 -218.5t57.5 -219t157 -158.5t216.5 -58q124 0 231 69l163 -164q27 -28 65 -28t65 27.5t27 66t-27 66.5zM443.5 50q-127.5 0 -217.5 91.5t-90 220t90 219.5t217.5 91
t217.5 -91t90 -219.5t-90 -220t-217.5 -91.5z" />
<glyph glyph-name="uniE618" unicode="&#xe618;"
d="M416 3q-38 0 -63 27l-187 200q-25 27 -22 65q2 36 29 62l362 337q7 7 15 5l275 -23q8 -1 13 -6.5t6 -13.5l4 -276q0 -9 -6 -15l-362 -337q-27 -25 -64 -25zM556 659zM740 578q-8 9 -19.5 9.5t-20 -7.5t-9 -20t7.5 -21t19.5 -9.5t20 7.5t9 20t-7.5 21zM740 578z" />
<glyph glyph-name="uniE61A" unicode="&#xe61a;"
d="M512 748q-91 0 -174 -35.5t-143 -95.5t-95.5 -143t-35.5 -174t35.5 -174t95.5 -143t143 -95.5t174 -35.5t174 35.5t143 95.5t95.5 143t35.5 174t-35.5 174t-95.5 143t-143 95.5t-174 35.5zM796.5 15.5q-55.5 -55.5 -127.5 -86.5q-75 -31 -157 -31t-157 31
q-72 31 -127.5 86.5t-86.5 127.5q-31 75 -31 157t31 157q31 72 86.5 127.5t127.5 86.5q75 31 157 31t157 -31q72 -31 127.5 -86.5t86.5 -127.5q31 -75 31 -157t-31 -157q-31 -72 -86.5 -127.5zM776 472q7 -7 7 -16.5t-7 -16.5l-154 -154l-17 -16l-148 -149l-6 -5
q-2 -3 -5 -5q-2 -1 -4.5 -1.5t-4.5 -0.5h-5q-2 0 -4 0.5t-4 1.5q-3 2 -5 5l-6 5l-165 165q-7 7 -7 16.5t7 16.5l5 5q7 7 16.5 7t16.5 -7l149 -149l132 133l17 16l154 154q7 7 16.5 7t16.5 -7z" />
<glyph glyph-name="uniE620" unicode="&#xe620;"
d="M1024 896h-1024v-1024h1024v1024zM448 101l-237 238l90 90l147 -146l307 306l90 -90l-397 -397v-1z" />
<glyph glyph-name="uniE629" unicode="&#xe629;"
d="M512 684q159 0 271.5 -112.5t112.5 -271.5t-112.5 -271.5t-271.5 -112.5t-271.5 112.5t-112.5 271.5t112.5 271.5t271.5 112.5zM512 812q-139 0 -257 -68.5t-186.5 -186.5t-68.5 -257t68.5 -257t186.5 -186.5t257 -68.5t257 68.5t186.5 186.5t68.5 257t-68.5 257
t-186.5 186.5t-257 68.5z" />
<glyph glyph-name="uniE639" unicode="&#xe639;"
d="M959 -62l-85 -85l-362 362l-362 -362l-85 85l362 362l-362 362l85 85l362 -362l362 362l85 -85l-362 -362z" />
<glyph glyph-name="uniE643" unicode="&#xe643;"
d="M387 158l45 -46l272 272l-272 272l-45 -46l226 -226z" />
<glyph glyph-name="uniE657" unicode="&#xe657;" horiz-adv-x="1304"
d="M1303 538l-161 242h-304v-443h233q19 0 32.5 14t13.5 33t-13.5 33t-32.5 14h-140v256h161l118 -177v-242h-442v577q0 21 -15 36t-36 15h-666q-21 0 -36 -15t-15 -36v-620q0 -21 15 -35.5t36 -14.5h142q-30 -49 -30 -105q0 -82 58 -140t140 -58t140 58t58 140
q0 56 -31 105h363q-30 -49 -30 -105q0 -82 58 -140t140 -58t140 58t58 140q0 56 -31 105h77v363zM93 803h582v-535h-582v535zM465 70q0 -43 -30.5 -74t-74 -31t-74 31t-30.5 74t30.5 74t74 31t74 -31t30.5 -74zM1164 70q0 -43 -31 -74t-74 -31t-74 31t-31 74t31 74t74 31
t74 -31t31 -74z" />
<glyph glyph-name="uniE663" unicode="&#xe663;"
d="M512 641l-392 -682h784zM512 583l341 -595h-682zM512 119zM512 77q-14 0 -23.5 9.5t-9.5 23t9.5 23t23.5 9.5t23.5 -9.5t9.5 -23t-9.5 -23t-23.5 -9.5zM512 175q-7 0 -11.5 9.5t-4.5 23.5l-17 148q0 13 9.5 22.5t23.5 9.5t23.5 -9.5t9.5 -22.5l-17 -148q0 -14 -4.5 -23.5
t-11.5 -9.5zM512 175z" />
<glyph glyph-name="uniE671" unicode="&#xe671;" horiz-adv-x="1025"
<glyph glyph-name="uniE610" unicode="&#xe610;"
d="M511 833q-91 0 -174 -36t-143 -96t-95.5 -143t-35.5 -174t35.5 -174t95.5 -143t143 -95.5t174 -35.5t174 35.5t143 95.5t95.5 143t35.5 174t-35.5 174t-95.5 143t-143 96t-174 36zM770 193q15 -14 15 -34t-14.5 -34t-34 -14t-33.5 14l-192 191l-192 -191q-14 -14 -34 -14
t-34 14t-14 34t14 34l192 191l-192 192q-14 14 -14 34t14 34t34 14t34 -14l192 -192l192 192q14 14 33.5 14t34 -14t14.5 -34t-15 -34l-191 -192z" />
<glyph glyph-name="uniE611" unicode="&#xe611;" horiz-adv-x="1025"
d="M512 812q-104 0 -199 -40.5t-163.5 -109t-109 -163.5t-40.5 -199t40.5 -199t109 -163.5t163.5 -109t199 -40.5t199 40.5t163.5 109t109 163.5t40.5 199t-40.5 199t-109 163.5t-163.5 109t-199 40.5zM806 433l-331 -331h-1q-11 -12 -27 -13.5t-29 6.5q-5 3 -9 7l-191 190
q-13 14 -13 33t13.5 32.5t32.5 13.5t33 -13l158 -158l298 298q14 14 33 14t32.5 -13.5t13.5 -33t-13 -32.5z" />
<glyph glyph-name="uniE673" unicode="&#xe673;"
d="M774 201h-524l262 366z" />
<glyph glyph-name="uniE674" unicode="&#xe674;"
d="M512 188l-280 392h560z" />
<glyph glyph-name="uniE675" unicode="&#xe675;"
d="M0 896h1024v-1024h-1024v1024zM85 -43h854v854h-854v-854z" />
<glyph glyph-name="uniE684" unicode="&#xe684;"
<glyph glyph-name="uniE612" unicode="&#xe612;"
d="M498 765q-105 0 -193.5 -51t-139.5 -139.5t-51 -193t51 -193t139.5 -140t193 -51.5t193 51.5t140 140t51.5 193t-51.5 193t-140 139.5t-192.5 51zM498 45q-92 0 -169 45t-122 122.5t-45 169t45 168.5t122 122t168.5 45t169 -45t122.5 -122t45 -168.5t-45 -169
t-122.5 -122.5t-168.5 -45zM498 45zM463 198q0 -15 10 -25t24.5 -10t25 10t10.5 24.5t-10.5 25t-25 10.5t-24.5 -10.5t-10 -24.5zM463 198zM498 605q-49 0 -83 -33.5t-34 -81.5q0 -10 7 -17t16.5 -7t16.5 7t7 17q0 29 20 49t50 20q29 0 49.5 -21t20.5 -50q0 -15 -39 -54
q-17 -17 -26.5 -27.5t-19 -29t-9.5 -36.5v-39q0 -10 7 -17t16.5 -7t16.5 7t7 17v39q0 19 41 60q17 17 26 27t17.5 27t8.5 33q0 49 -34 83t-82 34zM498 605z" />
<glyph glyph-name="uniE613" unicode="&#xe613;"
d="M512 -30q-112 0 -207.5 55.5t-151 151t-55.5 207.5t55.5 207.5t151 151t207.5 55.5t207.5 -55.5t151 -151t55.5 -207.5t-55.5 -207.5t-151 -151t-207.5 -55.5zM512 734q-95 0 -175.5 -47t-127.5 -127.5t-47 -175.5t47 -175.5t127.5 -127.5t175.5 -47t175.5 47
t127.5 127.5t47 175.5t-47 175.5t-127.5 127.5t-175.5 47zM633 259l-184 59v280h64v-233l140 -45z" />
<glyph glyph-name="uniE614" unicode="&#xe614;"
d="M911 725h-242v123q0 21 -13.5 34.5t-34.5 13.5h-246q-20 0 -33.5 -13.5t-13.5 -34.5v-123h-246q-21 0 -34.5 -13.5t-13.5 -34t13.5 -34t34.5 -13.5h293h243h293q21 0 34.5 13.5t13.5 34t-13.5 34t-34.5 13.5zM423 725v72h147v-72h-147zM765 579q-21 0 -34.5 -14
t-13.5 -34v-560h-441v560q0 20 -13.5 34t-34 14t-34 -14t-13.5 -34v-611q0 -21 13.5 -34.5t34.5 -13.5h536q20 0 33.5 13.5t13.5 34.5v611q3 20 -11.5 34t-35.5 14zM447 67v389q0 20 -13.5 33.5t-34 13.5t-34 -13.5t-13.5 -33.5v-389q0 -21 13.5 -34.5t34 -13.5t34 13.5
t13.5 34.5zM645 67v389q0 20 -13.5 33.5t-34.5 13.5q-20 0 -35.5 -13.5t-15.5 -33.5v-389q0 -21 13.5 -34.5t34.5 -13.5t36 13.5t15 34.5z" />
<glyph glyph-name="uniE68A" unicode="&#xe68a;"
d="M511 833q-91 0 -174 -36t-143 -96t-95.5 -143t-35.5 -174t35.5 -174t95.5 -143t143 -95.5t174 -35.5t174 35.5t143 95.5t95.5 143t35.5 174t-35.5 174t-95.5 143t-143 96t-174 36zM770 193q15 -14 15 -34t-14.5 -34t-34 -14t-33.5 14l-192 191l-192 -191q-14 -14 -34 -14
t-34 14t-14 34t14 34l192 191l-192 192q-14 14 -14 34t14 34t34 14t34 -14l192 -192l192 192q14 14 33.5 14t34 -14t14.5 -34t-15 -34l-191 -192z" />
<glyph glyph-name="uniE73F" unicode="&#xe73f;"
<glyph glyph-name="uniE615" unicode="&#xe615;"
d="M416 3q-38 0 -63 27l-187 200q-25 27 -22 65q2 36 29 62l362 337q7 7 15 5l275 -23q8 -1 13 -6.5t6 -13.5l4 -276q0 -9 -6 -15l-362 -337q-27 -25 -64 -25zM556 659zM740 578q-8 9 -19.5 9.5t-20 -7.5t-9 -20t7.5 -21t19.5 -9.5t20 7.5t9 20t-7.5 21zM740 578z" />
<glyph glyph-name="uniE616" unicode="&#xe616;"
d="M894 554l-43 43l-339 -339l-339 339l-43 -43l382 -383z" />
<glyph glyph-name="uniE753" unicode="&#xe753;" horiz-adv-x="1173"
<glyph glyph-name="uniE617" unicode="&#xe617;"
d="M838 185l40 39l-366 366l-366 -366l40 -39l326 326z" />
<glyph glyph-name="uniE618" unicode="&#xe618;"
d="M512 384zM64 384q0 91 35.5 174t95.5 143t143 95.5t174 35.5t174 -35.5t143 -95.5t95.5 -143t35.5 -174t-35.5 -174t-95.5 -143t-143 -95.5t-174 -35.5t-174 35.5t-143 95.5t-95.5 143t-35.5 174z" />
<glyph glyph-name="uniE619" unicode="&#xe619;" horiz-adv-x="1173"
d="M586 672q-28 65 -69 113t-86.5 73.5t-96 34t-97.5 -2t-90 -39.5t-75.5 -73t-51.5 -107.5t-20 -138.5q0 -41 9 -78.5t24 -66.5t39 -57.5t47 -48.5t55.5 -43t56.5 -38t58.5 -35.5t53.5 -33.5q93 -61 162 -138.5t82 -120.5q10 39 81.5 118.5t160.5 142.5q24 17 71.5 47
t79 50.5t71.5 54.5t64 67t41 81t16 102q0 75 -19.5 138t-52.5 105.5t-76.5 70.5t-91 37.5t-98 1t-96 -34.5t-85.5 -72.5t-67 -108.5z" />
<glyph glyph-name="uniE762" unicode="&#xe762;"
d="M512 384zM64 384q0 91 35.5 174t95.5 143t143 95.5t174 35.5t174 -35.5t143 -95.5t95.5 -143t35.5 -174t-35.5 -174t-95.5 -143t-143 -95.5t-174 -35.5t-174 35.5t-143 95.5t-95.5 143t-35.5 174z" />
<glyph glyph-name="uF0054" unicode="&#xf0054;"
d="M903 577l-68 69l-388 -388l-231 230l-68 -68l299 -298l65 65z" />
<glyph glyph-name="uF0066" unicode="&#xf0066;"
d="M493 487h69v-259h-69v259zM527.5 179q-17.5 0 -30 -12.5t-12.5 -30t12.5 -30t30 -12.5t30 12.5t12.5 30t-12.5 30t-30 12.5zM626 573q-27 11 -57 16t-68.5 3.5t-83 -19.5t-88.5 -52q-104 -83 -100 -225q13 -210 159 -272q72 -31 144 -30q39 0 90 14.5t94 47.5
q46 37 75 97.5t29 140.5q0 63 -17.5 113.5t-48 82.5t-61 51t-67.5 32zM518 58q-158 8 -211 144q-50 171 64 269q81 70 180 60q56 -10 97.5 -34.5t64.5 -56.5t34 -65t11 -67q0 -19 -1 -36.5t-6 -45t-14.5 -49.5t-27.5 -46t-42.5 -40t-63 -25.5t-85.5 -7.5z" />
<glyph glyph-name="uniE61A" unicode="&#xe61a;" horiz-adv-x="1304"
d="M1303 538l-161 242h-304v-443h233q19 0 32.5 14t13.5 33t-13.5 33t-32.5 14h-140v256h161l118 -177v-242h-442v577q0 21 -15 36t-36 15h-666q-21 0 -36 -15t-15 -36v-620q0 -21 15 -35.5t36 -14.5h142q-30 -49 -30 -105q0 -82 58 -140t140 -58t140 58t58 140
q0 56 -31 105h363q-30 -49 -30 -105q0 -82 58 -140t140 -58t140 58t58 140q0 56 -31 105h77v363zM93 803h582v-535h-582v535zM465 70q0 -43 -30.5 -74t-74 -31t-74 31t-30.5 74t30.5 74t74 31t74 -31t30.5 -74zM1164 70q0 -43 -31 -74t-74 -31t-74 31t-31 74t31 74t74 31
t74 -31t31 -74z" />
</font>
</defs></svg>
... ...
No preview for this file type
No preview for this file type
... ... @@ -6,6 +6,8 @@ var _dialog = dialog.Dialog,
_alert = dialog.Alert,
_confirm = dialog.Confirm;
var cascadingAddress = require('../plugins/cascading-address');
$('.alert-btn').click(function() {
new _alert('购买成功<br>进入 个人中心>我的订单<br>查看门票信息').show();
});
... ... @@ -44,4 +46,35 @@ $('.dialog-btn').click(function() {
}).show();
});
require('../plugins/tips');
lazyLoad($('img.lazy'));
var tips = $('#tips');
var change = $('#change');
var show = false;
change.click(function () {
"use strict";
if (show) {
tips.tips('hide');
show = false;
} else {
tips.tips("show", "×该用户已存在");
show = true;
}
});
$(function() {
// 运行此demo
// 1. 安装 npm i -g json-server
// 2. json-server --watch mock/address.json
cascadingAddress({
el: '#address',
url: 'http://localhost:3000/areas/0',
resource: 'areas'
});
});
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
require('./back/back');
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
require('./back/reset');
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
... ...
/**
* Created by TaoHuang on 2016/6/21.
*/
require('./back/verification');
... ...
/**
* 找回密码
* @author: xuqi<qi.xu@yoho.cn>
* @date: 2015/12/14
*/
var $ = require('yoho-jquery'),
regx = require('../common/mail-phone-regx'),
emailReg = regx.emailRegx,
phoneRegx = regx.phoneRegx;
var emailAc = require('../common/ac-email'); // 邮箱自动完成
var $cr = $('#country-code-hide'),
$phoneNum = $('#phone-num'),
$ca = $('#captcha'),
$ccList = $('#country-code-list'),
$cc = $('#country-code'),
$btn = $('#find-btn'),
$accErr = $('#account-err'),
$caErr = $('#captcha-err'),
caCount = 4, // 验证码位数
hasPh = false,
hasCa = false;
require('yoho-jquery-placeholder');
function imgcode() {
var time = new Date(),
$captchaImg = $('#captcha-img'),
captchaImgSrc = $captchaImg.attr('src').split('?')[0];
$('#captcha-img').attr('src', captchaImgSrc + '?t=' + time.getTime());
}
function enableBtn() {
if (hasPh && hasCa) {
$btn.removeClass('disable').prop('disabled', false);
} else {
$btn.addClass('disable').prop('disabled', true);
}
}
function authcode() {
if (!hasPh || !hasCa) {
enableBtn();
return;
}
$.ajax({
type: 'POST',
url: '/passport/back/authcode',
data: {
verifyCode: $.trim($ca.val()),
phoneNum: $phoneNum.val(),
area: $cr.val()
}
}).then(function(data) {
if (data.code === 200) {
hasCa = true;
} else if (data.code === 402) {
hasPh = false;
hasCa = true;
$accErr.removeClass('hide').find('em').text('该账号不存在');
$phoneNum.addClass('error');
} else if (data.code === 400) {
hasCa = false;
imgcode();
}
enableBtn();
});
}
function vaPn(v) {
var pass = true,
errTxt = '';
v = $.trim(v);
if (v !== '') {
if (/^[0-9]+$/.test(v)) {
if (phoneRegx[$cr.val()].test(v)) {
pass = true;
} else {
errTxt = '手机号码格式不正确, 请重新输入';
pass = false;
}
} else {
if (emailReg.test(v)) {
pass = true;
} else {
errTxt = '邮箱格式不正确, 请重新输入';
pass = false;
}
}
} else {
errTxt = '账户名不能为空';
pass = false;
}
hasPh = pass;
authcode();
return {
pass: pass,
errTxt: errTxt
};
}
function vaCa() {
var v = $.trim($ca.val());
if (v === '' || v.length < caCount) {
hasCa = false;
enableBtn();
return;
}
hasCa = true;
authcode();
}
emailAc($phoneNum, function() {
var pnVa = vaPn($phoneNum.val());
if (pnVa.pass) {
$accErr.addClass('hide');
$phoneNum.removeClass('error');
} else {
$accErr.removeClass('hide').find('em').text(pnVa.errTxt);
$phoneNum.addClass('error');
}
}
);
$ca.attr('maxlength', caCount);
// IE8 placeholder
$('input').placeholder();
$('#change-captcha, #captcha-img').on('click', function() {
imgcode();
});
$cc.on('click', function(e) {
e.stopPropagation();
if ($ccList.css('style') === 'block') {
$ccList.slideUp('fast');
} else {
$ccList.slideDown('fast');
}
});
$ccList.delegate('li', 'click', function(e) {
var $cur = $(this),
code = $cur.data('cc'),
pnVa;
e.stopPropagation();
$cr.val(code);
$cc.find('em').html($cur.text());
// 切换后验证手机号码
if ($.trim($phoneNum.val()) !== '') {
pnVa = vaPn($phoneNum.val());
enableBtn();
if (hasPh) {
$accErr.addClass('hide');
$phoneNum.removeClass('error');
} else {
$accErr.removeClass('hide').text(pnVa.errTxt);
$phoneNum.addClass('error');
}
}
$ccList.slideUp('fast');
});
$(document).click(function() {
if ($ccList.css('display') === 'block') {
$ccList.slideUp();
}
});
$phoneNum.keyup(function() {
vaPn($.trim($(this).val()));
}).focus(function() {
$(this).removeClass('error');
// focus隐藏错误提示
$accErr.addClass('hide');
});
// 验证码在鼠标移开后验证, keyup时不再验证
$ca.blur(function() {
var errTxt = $.trim($ca.val()) === '' ? '验证码不能为空' : '验证码不正确';
if (hasCa) {
$caErr.addClass('hide');
$ca.removeClass('error');
} else {
$caErr.removeClass('hide').find('em').text(errTxt);
$ca.addClass('error');
// 验证码错误则刷新验证码
if ($ca.val() < caCount) {
// 防止重复刷新验证码
imgcode();
}
}
}).focus(function() {
$(this).removeClass('error');
// focus隐藏错误提示
$caErr.addClass('hide');
}).keyup(function() {
vaCa();
});
$('#find-btn').click(function(e) {
if (/^[0-9]+$/.test($.trim($phoneNum.val()))) {
$('#find-form').attr('action', '/passport/back/mobile');
}
if ($(this).hasClass('disable')) {
return;
}
if (!hasCa || !hasPh) {
e.preventDefault();
return true;
}
});
... ...
/**
* 找回密码
* @author: xuqi<qi.xu@yoho.cn>
* @date: 2015/12/14
*/
var $ = require('yoho-jquery');
var $pwd = $('#pwd'),
$repwd = $('#re-input'),
$next = $('#reset-pwd-btn'),
$pwdErr = $('#pwd-err'),
$repwdErr = $('#repwd-err'),
$pwdTips = $('#pwd-tips');
var hasNoErrPw = false;
var $pwdIntensity = $('.pwd-intensity'),
$pwdParent = $pwdIntensity.closest('.pwd-intensity-container'),
$pwdTip1 = $('#pwd-tip1');
var pwdRegx = require('../common/mail-phone-regx').pwdValidateRegx;
require('yoho-jquery-placeholder');
/*
* 计算密码复杂度
*/
function gettype(str, i) {
if (str.charCodeAt(i) >= 48 && str.charCodeAt(i) <= 57) {
return 1;
} else if (str.charCodeAt(i) >= 97 && str.charCodeAt(i) <= 122) {
return 2;
} else if (str.charCodeAt(i) >= 65 && str.charCodeAt(i) <= 90) {
return 3;
}
return 4;
}
function isregular(cur, pre, type) {
var curCode = cur.charCodeAt(0);
var preCode = pre.charCodeAt(0);
if (curCode - preCode === 0) {
return true;
}
if (type !== 4 && (curCode - preCode === 1 || curCode - preCode === -1)) {
return true;
}
return false;
}
function getcomplex(curType, preType) {
if (preType === 0 || curType === preType) {
return 0;
} else if (curType === 4 || preType === 4) {
return 2;
} else {
return 1;
}
}
function computeComplex(password) {
var complex = 0,
length = password.length,
pre = '',
preType = 0,
i = 0,
cur,
curType;
for (i = 0; i < length; i++) {
cur = password.charAt(i);
curType = gettype(password, i);
if (preType !== curType || !isregular(cur, pre, curType)) {
complex += curType + getcomplex(curType, preType);
}
pre = cur;
preType = curType;
}
return complex;
}
function pwdKeyupEvt() {
var pwd = $pwd.val(),
pwdStrength = computeComplex(pwd),
level = 0;
// TODO:自定义密码强度规则,需要修正
if (pwdStrength === 0) {
level = 0;
} else if (pwdStrength <= 10) {
level = 1;
} else if (pwdStrength <= 20) {
level = 2;
} else {
level = 3;
}
switch (level) {
case 0:
$pwdParent.removeClass('red yellow green');
$pwdIntensity.removeClass('color');
break;
case 1:
$pwdParent.addClass('red').removeClass('yellow green');
$pwdIntensity.filter('.low').addClass('color');
$pwdIntensity.filter('.mid,.high').removeClass('color');
break;
case 2:
$pwdParent.addClass('yellow').removeClass('red green');
$pwdIntensity.filter('.low,.mid').addClass('color');
$pwdIntensity.filter('.high').removeClass('color');
break;
case 3:
$pwdParent.addClass('green').removeClass('yellow red');
$pwdIntensity.addClass('color');
break;
}
// 提示框
if (pwd === '') {
$pwdTip1.removeClass('red yes no').addClass('default');
} else if (pwd.length < 6 || pwd.length > 20) {
$pwdTip1.removeClass('default yes').addClass('no red');
} else {
$pwdTip1.removeClass('default no red').addClass('yes');
}
if (pwdRegx.test(pwd)) {
hasNoErrPw = true;
} else {
hasNoErrPw = false;
}
}
// IE8 placeholder
$('input').placeholder();
$('.va').keyup(function() {
var pass = true;
if ($(this).hasClass('pwd')) {
pwdKeyupEvt();
} else {
if ($(this).val() === '') {
pass = false;
}
}
if (pass && hasNoErrPw && $pwd.val() === $repwd.val()) {
pass = true;
} else {
pass = false;
}
if (pass) {
$next.removeClass('disable').prop('disabled', false);
} else {
$next.addClass('disable').prop('disabled', true);
}
}).blur(function() {
var $this = $(this),
v = $this.val();
if ($this.hasClass('pwd')) {
if (v === '') {
$this.addClass('error');
$pwdErr.removeClass('hide').find('em').text('请输入密码');
} else if (v.length < 6 || v.length > 20) {
$this.addClass('error');
$pwdErr.removeClass('hide').find('em').text('密码只支持6-20位');
} else if (!pwdRegx.test(v)) {
$this.addClass('error');
$pwdErr.removeClass('hide').find('em').text('密码须字母和数字组合');
} else {
$pwdErr.addClass('hide');
if ($repwd.val() !== '') {
if (v !== $repwd.val()) {
$repwd.addClass('error');
$repwdErr.removeClass('hide').find('em').text('两次密码输入不一致,请重新输入');
} else {
$repwd.removeClass('error');
$repwdErr.addClass('hide');
}
}
}
} else {
if (v === '') {
$this.addClass('error');
$repwdErr.removeClass('hide').find('em').text('请输入密码确认');
} else {
if ($pwd.val() !== '' && v !== $pwd.val()) {
$this.addClass('error');
$repwdErr.removeClass('hide').find('em').text('两次密码输入不一致,请重新输入');
} else {
$this.removeClass('error');
$repwdErr.addClass('hide');
}
}
}
}).focus(function() {
$(this).removeClass('error');
// focus后错误提示隐藏
if ($(this).hasClass('pwd')) {
$pwdErr.addClass('hide');
} else {
$repwdErr.addClass('hide');
}
});
$pwd.focus(function() {
$pwdErr.addClass('hide');
$pwdTips.removeClass('hide');
}).blur(function() {
$pwdTips.addClass('hide');
});
$('#pwd, #repwd').keydown(function(e) {
var code = e.keyCode || e.which;
// 空格输入过滤
if (code === 32) {
e.preventDefault();
return;
}
});
... ...
/**
* 验证手机
* @author: xuqi<qi.xu@yoho.cn>
* @date: 2015/12/14
*/
var $ = require('yoho-jquery');
var $sc = $('#send-captcha'),
$msgTip = $('#captcha-tip'),
$errTip = $('#err-tip'),
$next = $('#next-step'),
seconds,
itime;
$sc.click(function() {
$.post('/passport/back/sendbackmobile', {
mobile: $('#mobile').val(),
area: $('#area').val(),
verifyCode: $('#captchaPic').val()
}, function(jsonData) {
if (jsonData.code === 200) {
$errTip.hide();
if ($(this).hasClass('disable')) {
return;
}
seconds = 60;
// $sc.addClass('disable').prop('disabled', true);
$sc.addClass('disable').attr('disabled', true);
$msgTip.removeClass('hide');
$sc.val(seconds-- + '秒后可重新操作');
itime = setInterval(function() {
if (seconds === 0) {
clearInterval(itime);
// $sc.val('发送验证码').removeClass('disable').prop('disabled', false);
$sc.val('发送验证码').removeClass('disable').removeAttr('disabled');
} else {
$sc.val(seconds-- + '秒后可重新操作');
}
}, 1000);
} else {
$(this).addClass('error');
$errTip.removeClass('hide').text('发送失败');
}
});
});
seconds = 60;
// $sc.addClass('disable').prop('disabled', true);
$sc.addClass('disable').attr('disabled', true);
$msgTip.removeClass('hide');
$sc.val(seconds-- + '秒后可重新操作');
itime = setInterval(function() {
if (seconds === 0) {
clearInterval(itime);
// $sc.val('发送验证码').removeClass('disable').prop('disabled', false);
$sc.val('发送验证码').removeClass('disable').removeAttr('disabled');
} else {
$sc.val(seconds-- + '秒后可重新操作');
}
}, 1000);
$('#captcha').keyup(function() {
var v = $.trim($(this).val()),
that = this;
if (v.length === 4) {
$.ajax({
type: 'POST',
url: '/passport/back/backmobile',
dataType: 'json',
data: {
code: $('#captcha').val(),
verifyCode: $('#captchaPic').val(),
area: $('#area').val(),
mobile: $('#mobile').val()
},
success: function(res) {
if (res.code === 200) {
console.log(res.data);
// 添加验证码正确验证
$next.removeClass('disable').attr('href', res.data);
$errTip.addClass('hide');
$(that).removeClass('error');
} else {
$next.addClass('disable');
$errTip.removeClass('hide').find('em').text('验证码输入错误');
$(that).addClass('error');
}
}
});
} else {
$next.addClass('disable').attr('href', 'javascript:;');
}
}).blur(function() {
var v = $.trim($(this).val());
if (v === '') {
// 添加验证码正确验证
$(this).addClass('error');
$errTip.removeClass('hide').text('请输入验证码');
}
}).focus(function() {
$(this).removeClass('error');
});
... ...
/**
* 邮箱自动补全
* @author:xuqi<qi.xu@yoho.cn>
* @date: 2016/2/22
*/
var $ = require('yoho-jquery');
var mailPostfix = {
num: ['qq.com', '163.com', '126.com', 'sina.com', 'gmail.com', 'sohu.com', 'hotmail.com', '139.com', '189.com'],
other: ['gmail.com', 'qq.com', '163.com', '126.com', 'sina.com', 'sohu.com', 'hotmail.com', '139.com', '189.com']
};
var emailAcTime;
/**
* @param $input 需要自动完成的$对象
* @param cb 鼠标移开/点击自动完成项后需要执行的操作(验证等)
*/
module.exports = function($input, cb) {
var ulHtml = '<ul id="email-autocomplete" class="email-autocomplete hide"></ul>';
var $emailAutoComplete;
$input.parent().append(ulHtml);
$emailAutoComplete = $('#email-autocomplete');
$input.on('keyup', function() {
var account = $.trim($(this).val()),
html = '',
accountMatch,
matchStr,
postfix,
i;
// 输入@时自动补全邮箱后缀
// 此处>0非错误,用于避免输入的第一个字符为@被识别为邮箱
if (account.indexOf('@') > 0) {
accountMatch = account.match(/^[0-9]+@(.*)/);
if (accountMatch) {
// 数字邮箱补全
postfix = mailPostfix.num;
matchStr = accountMatch[1];
} else {
postfix = mailPostfix.other;
matchStr = account.match(/@(.*)/)[1];
}
for (i = 0; i < postfix.length; i++) {
if (postfix[i].indexOf(matchStr) > -1) {
html += '<li>' + account.slice(0, account.indexOf('@')) + '@' + postfix[i] + '</li>';
}
}
if (html !== '' && /.com$/.test(account) === false) {
$emailAutoComplete.html(html).removeClass('hide');
} else {
// 隐藏autocomplete
$emailAutoComplete.html('').addClass('hide');
}
}
}).on('blur', function() {
emailAcTime = setTimeout(function() {
// 未点击自动完成项
$emailAutoComplete.addClass('hide');
cb && cb();
}, 200);
});
// 邮箱自动完成列表项点击
$emailAutoComplete.on('click', 'li', function() {
clearTimeout(emailAcTime); // 清空默认关闭
// 点击自动完成项后进行验证
$input.val($(this).text()).focus();
$emailAutoComplete.addClass('hide');
cb && cb();
});
};
... ...
/**
* 国家区号Map手机号码以及邮箱验证正则
* @author: xuqi<qi.xu@yoho.cn>
* @date: 2015/12/11
*/
var countryPhoneRegx = {
'+86': /^1[35847]{1}[0-9]{9}$/,
'+852': /^[965]{1}[0-9]{7}$/,
'+853': /^[0-9]{8}$/,
'+886': /^[0-9]{10}$/,
'+65': /^[98]{1}[0-9]{7}$/,
'+60': /^1[1234679]{1}[0-9]{8}$/,
'+1': /^[0-9]{10}$/,
'+82': /^01[0-9]{9}$/,
'+44': /^7[789]{1}[0-9]{8}$/,
'+81': /^0[9|8|7][0-9]{9}$/,
'+61': /^[0-9]{11}$/
};
var emailRegx = /^[.\-_a-zA-Z0-9]+@[\-_a-zA-Z0-9]+\.[a-zA-Z0-9]/;
var pwdValidateRegx = /^(?![0-9]+$)(?![a-zA-Z]+$)[0-9A-Za-z]{6,20}$/;
exports.phoneRegx = countryPhoneRegx;
exports.emailRegx = emailRegx;
exports.pwdValidateRegx = pwdValidateRegx;
... ...
/*
* 计算密码复杂度
*/
function gettype(str, i) {
if (str.charCodeAt(i) >= 48 && str.charCodeAt(i) <= 57) {
return 1;
} else if (str.charCodeAt(i) >= 97 && str.charCodeAt(i) <= 122) {
return 2;
} else if (str.charCodeAt(i) >= 65 && str.charCodeAt(i) <= 90) {
return 3;
}
return 4;
}
function isregular(cur, pre, type) {
var curCode = cur.charCodeAt(0);
var preCode = pre.charCodeAt(0);
if (curCode - preCode === 0) {
return true;
}
if (type !== 4 && (curCode - preCode === 1 || curCode - preCode === -1)) {
return true;
}
return false;
}
function getcomplex(curType, preType) {
if (preType === 0 || curType === preType) {
return 0;
} else if (curType === 4 || preType === 4) {
return 2;
} else {
return 1;
}
}
function computeComplex(password) {
var complex = 0,
length = password.length,
pre = '',
preType = 0,
i = 0,
cur,
curType;
for (i = 0; i < length; i++) {
cur = password.charAt(i);
curType = gettype(password, i);
if (preType !== curType || !isregular(cur, pre, curType)) {
complex += curType + getcomplex(curType, preType);
}
pre = cur;
preType = curType;
}
return complex;
}
module.exports = computeComplex;
... ...
require('./login/index');
... ...
/**
* 登录
* @author: xuqi<qi.xu@yoho.cn>
* @date: 2015/12/11
*/
var $ = require('yoho-jquery');
var $account = $('#account'),
$password = $('#password'),
$captcha = $('#captcha');
var $accountTip = $account.siblings('.err-tip'),
$passwordTip = $password.siblings('.err-tip'),
$captchaTip = $captcha.siblings('.err-tip'),
$capsLock = $('#caps-lock');
var $countryCodeHide = $('#country-code-hide'),
$countryCodeEm = $('#country-code > em'),
$countryList = $('#country-list');
var $emailAutoComplete = $('#email-autocomplete');
var mailPhoneRegx = require('../common/mail-phone-regx');
var mailAc = require('../common/ac-email'); // 邮箱自动完成
var $remember = $('.remember-me');
var captchaUrl = '/passport/images?t='; // /passport/images?t=1454464125
var $captchaWrap = $('.captcha-wrap'),
$captchaImg = $captchaWrap.find('#captcha-img');
// checkbox status unicode
var checkbox = {
checked: '&#xe612;',
unchecked: '&#xe613;'
};
var authing = false;
var emailAcTime;
$captcha = $captchaWrap.find('#captcha');
$captchaTip = $captchaWrap.find('.err-tip');
require('yoho-jquery-placeholder');
// 验证账户名
function validateAccount() {
var pass = false,
account = $.trim($account.val()),
countryCode = $countryCodeHide.val(),
err;
if (account !== '') {
if (/^[0-9]+$/.test(account)) {
// 如果是纯数字,则作为手机号码处理
if (countryCode !== '+86' ||
mailPhoneRegx.phoneRegx[countryCode].test(account)) {
pass = true;
} else {
pass = false;
err = '手机号码不正确,请重新输入';
}
} else {
if (mailPhoneRegx.emailRegx.test(account)) {
pass = true;
} else {
pass = false;
err = '邮箱格式不正确,请重新输入';
}
}
} else {
err = '请输入账户名';
}
if (pass) {
$accountTip.addClass('hide');
$account.removeClass('error');
} else {
$accountTip.removeClass('hide').children('em').text(err);
$account.addClass('error');
}
return pass;
}
// 验证密码
function validatePassword() {
var pass = false,
password = $.trim($password.val()),
err;
if (password !== '') {
if (password.length < 6) {
err = '请输入长度为6-20字符的密码';
} else {
pass = true;
}
} else {
err = '请输入密码';
}
if (pass) {
$passwordTip.addClass('hide');
$password.removeClass('error');
} else {
$passwordTip.removeClass('hide').children('em').text(err);
$password.addClass('error');
}
return pass;
}
// 验证验证码
function validateCaptcha() {
var pass = false,
captcha = $.trim($captcha.val()),
err;
// 验证码不可见的时候不验证
if ($captchaWrap.is(':hidden')) {
return true;
}
if (captcha !== '') {
if (captcha.length !== 4) {
err = '请输入长度为4字符的验证码';
} else {
pass = true;
}
} else {
err = '请输入验证码';
}
if (pass) {
$captchaTip.addClass('hide');
$captcha.removeClass('error');
} else {
$captchaTip.removeClass('hide').children('em').text(err);
$captcha.addClass('error');
}
return pass;
}
// 验证
function validate() {
var pass = true,
account = $.trim($account.val()),
password = $.trim($password.val());
if (account !== '') {
pass = validateAccount() && validatePassword() && validateCaptcha();
} else {
pass = false;
$account.addClass('error');
if (password === '') {
// 账户名和密码都为空的情况下点击登陆,只在账户输入框后显示错误提示
$accountTip.addClass('both-error').removeClass('hide').children('em').text('请输入账户名和密码');
$passwordTip.addClass('hide');
$password.addClass('error');
} else {
$accountTip.removeClass('hide').children('em').text('请输入账户名');
}
}
return pass;
}
// 密码错误次数,超过三次显示验证码
function vaAccountErrTimes() {
$captchaImg.attr('src', captchaUrl + $.now());
$captcha.val('');
$captchaWrap.removeClass('hide');
}
// 登录
function login() {
var pass = validate();
if (pass && authing === false) {
authing = true;
$.ajax({
url: '/passport/login/auth',
type: 'POST',
data: {
areaCode: $countryCodeHide.val().replace('+', ''),
account: $.trim($account.val()),
password: $.trim($password.val()),
captcha: $.trim($captcha.val()),
isRemember: $remember.hasClass('checked') ? true : false
},
success: function(res) {
if (res.code === 200) {
if (res.data) {
// 防止data.data为undefined时下行语句执行出错而导致脚本不能走到complete去处理authing
location.href = res.data.session;
}
} else {
if (res.data.errorType === 'captcha') {
$captchaTip.removeClass('hide').children('em').html(res.message);
$captcha.addClass('error').val('');
} else {
$passwordTip.removeClass('hide').children('em').html(res.message);
$password.addClass('error').val('');
}
// 验证错误次数
if (res.data && res.data.needCaptcha) {
vaAccountErrTimes();
}
}
},
complete: function() {
authing = false;
}
});
}
}
mailAc($account, function() {
if (validateAccount()) {
$.ajax({
url: '/passport/login/account',
type: 'GET',
data: {
account: $.trim($account.val())
}
}).then(function(res) {
if (res.data && res.data.needCaptcha) {
vaAccountErrTimes();
}
});
}
}
);
$('[placeholder]').placeholder();
// 展开地区列表
$('#country-code').on('click', function() {
if ($countryList.css('display') === 'none') {
$countryList.slideDown();
}
});
// 选中地区列表项
$countryList.on('click', 'li', function() {
var $this = $(this),
cc = $this.data('cc');
$countryCodeEm.html($this.html());
$countryCodeHide.val(cc);
$countryList.slideUp();
});
// 点击其他区域,收起区域列表
$(document).on('click', function(e) {
if ($(e.target).closest('#country-code').length > 0) {
return;
}
if ($countryList.css('display') === 'block') {
$countryList.slideUp();
}
});
// 密码
$password.on('blur', function() {
validatePassword();
if ($capsLock.hasClass('hide')) {
return;
}
$capsLock.addClass('hide');
}).on('keypress', function(e) {
var code = e.which;
// CapsLock检测
if (code >= 65 && code <= 90) {
$capsLock.removeClass('hide');
return;
}
$capsLock.addClass('hide');
});
// 验证码
$captcha.on('blur', function() {
validateCaptcha();
});
// 邮箱自动完成列表项点击
$emailAutoComplete.on('click', 'li', function() {
clearTimeout(emailAcTime); // 清空默认关闭
$account.val($(this).text()).focus();
$emailAutoComplete.addClass('hide');
});
// 记住登录状态
$remember.on('click', function() {
var $this = $(this);
$this.toggleClass('checked');
if ($this.hasClass('checked')) {
$this.children('i').html(checkbox.checked);
} else {
$this.children('i').html(checkbox.unchecked);
}
});
// focus到输入框则隐藏错误提示和样式
$('.va').on('focus', function() {
var $this = $(this);
$this.removeClass('error');
$this.siblings('.err-tip').addClass('hide');
});
// 验证码刷新
$captchaWrap.on('click', '.change-captcha, .captcha-img', function() {
$captchaImg.attr('src', captchaUrl + $.now());
});
// 登录
$('#login-btn').on('click', login);
// Enter登录
$('input.va').on('keypress', function(e) {
if (e.which === 13) {
login();
}
});
// 初始:只带账户名的页面,密码输入获得焦点
if (($account.val() !== '' || $account.val() === $account.attr('placeholder')) &&
$password.val() === '') {
$password.focus();
}
... ...
var reg = require('./reg/reg');
reg.init('reg');
... ...