Authored by 姜枫

add local login

... ... @@ -5,13 +5,65 @@
*/
'use strict';
const _ = require('lodash');
const passport = require('passport');
const WeixinStrategy = require('passport-weixin');
const LocalStrategy = require('passport-local').Strategy;
const config = require('../../config/common');
const md5 = require('md5');
const AuthHelper = require('./models/auth-helper');
const config = global.yoho.config;
const helpers = global.yoho.helpers;
const cookie = global.yoho.cookie;
const logger = global.yoho.logger;
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);
AuthHelper.signin(area, username, password, shoppingKey).then((result) => {
if (result.code && result.code === 200 && result.data.uid) {
done(null, result.data);
} else {
done('账号或密码不正确', null);
}
}).catch(e => {
logger.error('call the signin service fail,', e);
done('登录失败,请稍后重试', null);
});
}));
/**
* wechat登录
*/
... ...
... ... @@ -5,16 +5,21 @@
*/
'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, next, user) {
let shoppingKey = cookie.getShoppingKey(req);
... ... @@ -67,7 +72,7 @@ function doPassportCallback(req, res, next, user) {
}
}
const wechat = {
const common = {
beforeLogin: (req, res, next) => {
let refer = req.query.refer;
... ... @@ -79,6 +84,103 @@ const wechat = {
});
next();
},
ipFilter: (req, res, next) => {
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 errLoginTimes = cache.get(errorLoginKey) || 0;
let accountTimes = cache.get(accountKey) || 0;
let ipTimes = cache.get(ipKey) || 0;
if (accountTimes >= 10) {
res.json({ code: 400, message: '您的账号已被暂时锁定,请稍后再试', data: '' });
} else if (ipTimes >= 100) {
res.json({ code: 400, message: '您尝试的次数过多,账号已被暂时锁定,请稍后再试', data: '' });
} else {
return next();
}
}
};
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'),
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,
data: ''
});
} else {
let refer = req.cookies.refer;
if (refer) {
refer = decodeURI(req.cookies.refer);
} else {
refer = `${config.siteUrl}/home`;
}
if (/sign|login/.test(refer)) {
refer = `${config.siteUrl}/home`;
}
user.session = refer;
user.href = refer;
AuthHelper.syncUserSession(user.uid, req, res).then(() => {
res.json({
code: 200,
data: user
});
});
}
})(req, res, next);
}
};
const wechat = {
login: (req, res, next) => {
return passport.authenticate('wechat', {
state: uuid.v4()
... ... @@ -102,4 +204,6 @@ const wechat = {
}
};
exports.common = common;
exports.wechat = wechat;
exports.local = local;
... ...
... ... @@ -5,6 +5,21 @@ const api = global.yoho.API;
class Auth {
static signin(area, profile, password, shoppingKey) {
let param = {
method: 'app.passport.signin',
area: area,
profile: profile,
password: password
};
if (shoppingKey) {
param.shopping_key = shoppingKey;
}
return api.post('', param);
}
static signinByOpenID(nickname, openId, sourceType, shoppingKey) {
let param = {
nickname: nickname,
... ...
... ... @@ -7,15 +7,17 @@
'use strict';
const express = require('express');
const cRoot = './controllers';
const login = require(cRoot + '/login');
const cRoot = './controllers';
const login = require(cRoot + '/login');
const Captcha = require(cRoot + '/captcha');
const Back = require(cRoot + '/back');
const Back = require(cRoot + '/back');
const router = express.Router(); // eslint-disable-line
router.get('/autosign/wechat', login.wechat.beforeLogin, login.wechat.login); // 微信登录, 兼容 PHP 的路径
router.get('/login', login.common.beforeLogin, login.local.loginPage);
router.get('/autosign/wechat', login.common.beforeLogin, login.wechat.login); // 微信登录, 兼容 PHP 的路径
router.get('/login/wechat/callback', login.wechat.callback);
/**
... ...
<div class="login-page passport-page yoho-page clearfix">
{{# passport}}
{{> login/cover}}
<div class="content">
<ul class="login-ul">
<li class="relative clearfix">
<h2 class="title">会员登录</h2>
<span id="country-code" class="country-code right">
<em>{{countryName}} {{countryCode}}</em>
<i class="iconfont">&#xe600;</i>
<ul id="country-list" class="country-list">
{{#each countryList}}
<li data-cc="{{areaCode}}" {{# selected}}selected{{/selected}}>{{name}} {{areaCode}}</li>
{{/each}}
</ul>
</span>
</li>
<li class="relative">
<input id="account" class="account input va" name="account" value="{{bindMobile}}" type="text" placeholder="邮箱/手机号码" autocomplete="off">
<span class="err-tip hide">
<i></i>
<em></em>
</span>
</li>
<li class="relative">
<input id="password" class="password input va" name="password" type="password" placeholder="密码" autocomplete="off" maxlength="20">
<span id="caps-lock" class="caps-lock hide">大写状态开启</span>
<span class="err-tip hide">
<i></i>
<em>请输入密码</em>
</span>
</li>
<li class="clearfix captcha-wrap hide">
<input id="captcha" class="input va captcha" type="text" name="captcha" placeholder="图形验证码" autocomplete="off" maxlength="4">
<img id="captcha-img" class="captcha-img" alt="">
<a class="link change-captcha">换一张</a>
<span class="err-tip hide">
<i></i>
<em></em>
</span>
</li>
<li>
<span id="login-btn" class="login-btn btn">登录</span>
</li>
<li class="other-opts">
<span class="remember-me">
<i class="iconfont">&#xe613;</i>
记住登录状态
</span>
<span class="right">
<a class="forget-password" href="{{forgetPwd}}">忘记密码?</a>
|
<a class="fast-reg" href="{{fastReg}}">快速注册</a>
</span>
</li>
<li class="third-party-login">
<a href="{{weixinLogin}}">
<span class="icon weixin"></span>
</a>
<a href="{{qqLogin}}">
<span class="icon qq"></span>
</a>
<a href="{{weiboLogin}}">
<span class="icon weibo"></span>
</a>
<a href="{{alipayLogin}}">
<span class="icon alipay"></span>
</a>
<a href="{{doubanLogin}}">
<span class="icon douban"></span>
</a>
<a href="{{renrenLogin}}">
<span class="icon renren"></span>
</a>
</li>
</ul>
<input id="country-code-hide" name="countryCode" type="hidden" value="{{countryCode}}">
</div>
{{/ passport}}
</div>
... ...
<div class="passport-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>
... ...
... ... @@ -46,6 +46,7 @@
"morgan": "^1.7.0",
"oneapm": "^1.2.20",
"passport": "^0.3.2",
"passport-local": "^1.0.0",
"passport-weixin": "^0.1.0",
"request-promise": "^3.0.0",
"serve-favicon": "^2.3.0",
... ... @@ -93,6 +94,7 @@
"yoho-handlebars": "^4.0.5",
"yoho-jquery": "^1.12.4",
"yoho-jquery-lazyload": "^1.9.7",
"yoho-jquery-placeholder": "0.0.3",
"yoho-slider": "0.0.2"
}
}
... ...
/**
* 找回密码
* @author: xuqi<qi.xu@yoho.cn>
* @date: 2015/12/14
*/
var $ = require('yoho-jquery'),
regx = require('./mail-phone-regx'),
emailReg = regx.emailRegx,
phoneRegx = regx.phoneRegx;
var emailAc = require('./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'),
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: 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();
});
};
/**
* 邮箱自动补全
* @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;
\ No newline at end of file
/**
* 国家区号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;
... ...
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();
}
... ...