Authored by 陈轩

save

  1 +/* eslint no-unused-vars: ["error", { "args": "none" }] */
1 /** 2 /**
2 * 登录 3 * 登录
3 * @author: Bi Kai<kai.bi@yoho.cn> 4 * @author: Bi Kai<kai.bi@yoho.cn>
@@ -93,6 +94,7 @@ const local = { @@ -93,6 +94,7 @@ const local = {
93 backUrl: 'javascript:history.go(-1)', // eslint-disable-line 94 backUrl: 'javascript:history.go(-1)', // eslint-disable-line
94 showHeaderImg: true, // 控制显示头部图片 95 showHeaderImg: true, // 控制显示头部图片
95 isPassportPage: true, // 模板中模块标识 96 isPassportPage: true, // 模板中模块标识
  97 + smsLoginUrl: '/passport/sms_login',
96 registerUrl: '/passport/reg/index', // 注册的URL链接 98 registerUrl: '/passport/reg/index', // 注册的URL链接
97 aliLoginUrl: '/passport/login/alipay', // 支付宝快捷登录的URL链接 99 aliLoginUrl: '/passport/login/alipay', // 支付宝快捷登录的URL链接
98 weiboLoginUrl: '/passport/login/sina', // 微博登录的URL链接 100 weiboLoginUrl: '/passport/login/sina', // 微博登录的URL链接
  1 +/* eslint no-unused-vars: ["error", { "args": "none" }] */
  2 +'use strict';
  3 +const RegService = require('../models/reg-service');
  4 +const PhoneService = require('../models/phone-service');
  5 +
  6 +// constrant
  7 +const CODE_REQUIRED = '请输入校验码';
  8 +
  9 +// 短信登录 第一步: 展现页面
  10 +const step1 = (req, res, next) => {
  11 + let template = 'sms/login';
  12 + let viewData = {
  13 + module: 'passport',
  14 + page: 'sms-login',
  15 + isPassportPage: true,
  16 + headerText: '手机号码快捷登录',
  17 + areaCode: '+86', // 默认的区号
  18 + countrys: RegService.getAreaData() // 地区信息列表
  19 + };
  20 +
  21 + res.render(template, viewData);
  22 +};
  23 +
  24 +// 短信登录 第二步: 输入 校验码
  25 +const step2 = (req, res, next) => {
  26 + const mobile = req.session.sms_login.mobile;
  27 + const area = req.session.sms_login.area;
  28 +
  29 + const template = 'sms/check';
  30 + const viewData = {
  31 + module: 'passport',
  32 + page: 'sms-check',
  33 + isPassportPage: true,
  34 + headerText: '手机号码快捷登录',
  35 + interval: 60,
  36 + mobile,
  37 + area
  38 + };
  39 +
  40 + res.render(template, viewData);
  41 +};
  42 +
  43 +// 短信登录 第二步: 设置密码 (针对 改手机未注册用户)
  44 +const step3 = (req, res, next) => {
  45 +
  46 +};
  47 +
  48 +// 短信 登录
  49 +exports.loginPage = (req, res, next) => {
  50 + let step = Number(req.query.step) || 1;
  51 +
  52 + switch (step) {
  53 + case 2: step2(req, res, next); break;
  54 + case 3: step3(req, res, next); break;
  55 + case 1:
  56 + default:
  57 + step1(req, res, next);
  58 + }
  59 +};
  60 +
  61 +exports.token = (req, res, next) => {
  62 + if (!req.xhr) {
  63 + return next(404);
  64 + }
  65 +
  66 + let area = req.query.area;
  67 + let mobile = req.query.mobile;
  68 +
  69 + PhoneService.sendSMS({ mobile, area }).then(result => {
  70 + if (result.code === 200) {
  71 + req.session.sms_login = {
  72 + area,
  73 + mobile
  74 + };
  75 +
  76 + result.redirect = '/passport/sms_login?step=2';
  77 + res.json(result);
  78 + return;
  79 + }
  80 +
  81 + res.json(result);
  82 + });
  83 +};
  84 +
  85 +exports.check = (req, res, next) => {
  86 + if (!req.xhr) {
  87 + return next(404);
  88 + }
  89 + // TODO: 防御性
  90 +
  91 + let code = req.query.code || '';
  92 + const mobile = req.session.sms_login.mobile;
  93 + const area = req.session.sms_login.mobile;
  94 +
  95 + code = code.trim();
  96 + if (!code) {
  97 + res.json({
  98 + code: 404,
  99 + message: CODE_REQUIRED
  100 + });
  101 + }
  102 +
  103 + Promise.all([
  104 + PhoneService.checkUserPhoneExist(mobile, area),
  105 + PhoneService.verifySMS(mobile, area, code)
  106 + ])
  107 + .then(result => {
  108 + let r1 = result[0] || {};
  109 + let r2 = result[1] || {};
  110 + let redirect;
  111 +
  112 + // 验证码 校验失败
  113 + if (r2.code !== 200) {
  114 + res.json(r2);
  115 + return;
  116 + }
  117 +
  118 + // 检测 手机号 是否注册 异常
  119 + if (r1.code !== 200) {
  120 + res.json(r1);
  121 + return;
  122 + }
  123 +
  124 + if (r1.data.is_register === 'Y') {
  125 + redirect = '';
  126 + } else {
  127 + redirect = '/passport/sms_login?step=3';
  128 + }
  129 +
  130 + res.json({
  131 + code: 200,
  132 + redirect
  133 + });
  134 + })
  135 + .catch();
  136 +};
  137 +
  138 +exports.password = (req, res, next) => {
  139 + res.render('sms/password', {
  140 + module: 'passport',
  141 + page: 'sms-login',
  142 + isPassportPage: true,
  143 + headerText: '设置密码',
  144 + });
  145 +};
  146 +
  1 +/* eslint no-unused-vars: ["error", { "args": "none" }] */
  2 +'use strict';
  3 +const API = global.yoho.API;
  4 +
  5 +class PhoneService {
  6 + // 校验 手机 是否 已注册
  7 + // http://git.yoho.cn/yoho-documents/api-interfaces/blob/master/个人中心/验证码登录/校验是否是注册用户.md
  8 + static checkUserPhoneExist(mobile, area) {
  9 + return API.get('', {
  10 + method: 'app.passport.checkUserExist',
  11 + mobile,
  12 + area
  13 + });
  14 + }
  15 +
  16 + // 手机号 自动登录
  17 + // http://git.yoho.cn/yoho-documents/api-interfaces/blob/master/个人中心/验证码登录/手机号自动登录.md
  18 + static autoSignin(profile, area) {
  19 + return API.get('', {
  20 + method: 'app.passport.autoSignin',
  21 + profile,
  22 + area
  23 + });
  24 + }
  25 +
  26 + // 发送 验证码
  27 + // http://git.yoho.cn/yoho-documents/api-interfaces/blob/master/个人中心/验证码登录/发送验证码.md
  28 + static sendSMS(mobile, area, type) {
  29 + if (process.env.NODE_ENV === 'development') {
  30 + return new Promise((resolve, reject) => {
  31 + return resolve({
  32 + alg: 'SALT_MD5',
  33 + code: 200,
  34 + data: {},
  35 + md5: '6d729d4b35f10fc73531210bd7ecff91',
  36 + message: '发送成功.'
  37 + });
  38 + });
  39 + }
  40 +
  41 + return API.get('', {
  42 + method: 'app.message.sendSms',
  43 + mobile,
  44 + area,
  45 + type
  46 + });
  47 + }
  48 +
  49 + // 校验 验证码
  50 + // http://git.yoho.cn/yoho-documents/api-interfaces/blob/master/个人中心/验证码登录/验证验证码.md
  51 + static verifySMS(mobile, area, code) {
  52 + if (process.env.NODE_ENV === 'development') {
  53 + return new Promise((resolve, reject) => {
  54 + return resolve({
  55 + alg: 'SALT_MD5',
  56 + code: 200,
  57 + data: {
  58 + is_pass: 'Y'
  59 + },
  60 + md5: '6d729d4b35f10fc73531210bd7ecff91',
  61 + message: '发送成功.'
  62 + });
  63 + });
  64 + }
  65 +
  66 + return API.get('', {
  67 + method: 'app.user.verifySmsCode',
  68 + mobile,
  69 + area,
  70 + code
  71 + });
  72 + }
  73 +}
  74 +
  75 +module.exports = PhoneService;
@@ -12,6 +12,7 @@ const login = require(cRoot + '/login'); @@ -12,6 +12,7 @@ const login = require(cRoot + '/login');
12 const back = require(cRoot + '/back'); 12 const back = require(cRoot + '/back');
13 const bind = require(cRoot + '/bind'); 13 const bind = require(cRoot + '/bind');
14 const reg = require(cRoot + '/reg'); 14 const reg = require(cRoot + '/reg');
  15 +const smsLogin = require(cRoot +'/sms');
15 16
16 17
17 const router = express.Router(); // eslint-disable-line 18 const router = express.Router(); // eslint-disable-line
@@ -33,6 +34,12 @@ router.get('/passport/international', login.common.beforeLogin, login.local.inte @@ -33,6 +34,12 @@ router.get('/passport/international', login.common.beforeLogin, login.local.inte
33 // 本地登录 34 // 本地登录
34 router.post('/passport/login/auth', login.local.login); 35 router.post('/passport/login/auth', login.local.login);
35 36
  37 +// SMS 短信
  38 +router.get('/passport/sms_login', smsLogin.loginPage);
  39 +router.get('/passport/sms_login/token', smsLogin.token); // only ajax;
  40 +router.get('/passport/sms_login/check', smsLogin.check);
  41 +router.get('/passport/sms_login/password', smsLogin.password);
  42 +
36 // 微信登录 43 // 微信登录
37 router.get('/passport/login/wechat', login.common.beforeLogin, login.wechat.login); 44 router.get('/passport/login/wechat', login.common.beforeLogin, login.wechat.login);
38 router.get('/passport/login/wechat/callback', login.wechat.callback); 45 router.get('/passport/login/wechat/callback', login.wechat.callback);
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 </div> 10 </div>
11 <span id="btn-login" class="btn btn-login disable">登录</span> 11 <span id="btn-login" class="btn btn-login disable">登录</span>
12 <p class="op-container"> 12 <p class="op-container">
13 - <a class="go-register" href={{registerUrl}}>免费注册</a> 13 + <a class="sms-login" href={{smsLoginUrl}}>手机号码快捷登录</a>
14 <span id="forget-pwd" class="forget-pwd">忘记密码</span> 14 <span id="forget-pwd" class="forget-pwd">忘记密码</span>
15 </p> 15 </p>
16 <div class="third-party-login"> 16 <div class="third-party-login">
@@ -22,6 +22,10 @@ @@ -22,6 +22,10 @@
22 </div> 22 </div>
23 </div> 23 </div>
24 <a class="international" href={{internationalUrl}}>International Customer</a> 24 <a class="international" href={{internationalUrl}}>International Customer</a>
  25 + <div class="go-register">
  26 + <i class="iconfont">&#xe610;</i>
  27 + <a href={{registerUrl}}>注册Yoho!Family</a>
  28 + </div>
25 <div class="login-tip"> 29 <div class="login-tip">
26 <div class="info-icon"></div> 30 <div class="info-icon"></div>
27 Yoho!Family账号可登录Yoho!Buy有货 31 Yoho!Family账号可登录Yoho!Buy有货
  1 +<div class="sms-login passport-page yoho-page">
  2 + {{> passport/header}}
  3 + <div class="content">
  4 + <p class="sms-login__msg">验证码已发至&nbsp;<span class="tel">+86 133601454888</span></p>
  5 + <div class="input-container input-group sms-input row">
  6 + <input id="sms-code" class="input" type="text" placeholder="验证码">
  7 + <span class="input-addon">
  8 + <button type="button" id="resend-sms">重发验证码</button>
  9 + </span>
  10 + </div>
  11 + <button id="btn-next" class="btn btn-next disable row" type="button">登录</button>
  12 + </div>
  13 + <input type="hidden" name="mobile" value="{{mobile}}">
  14 + <input type="hidden" name="area" value="{{area}}">
  15 +</div>
  16 +<script>
  17 + var interval = {{interval}};
  18 +</script>
  1 +<div class="sms-login-page passport-page yoho-page">
  2 + {{> passport/header}}
  3 + <div class="content">
  4 + {{> passport/country_list}}
  5 + <div class="input-container phone-container row has-clear">
  6 + <span id="area-code" class="area-code">{{areaCode}}</span>
  7 + <input id="phone-num" class="input phone-num" type="text" placeholder="手机号">
  8 + </div>
  9 + <span id="btn-next" class="btn btn-next disable row">获取短信验证码</span>
  10 + </div>
  11 +</div>
  1 +<div class="sms-login passport-page yoho-page">
  2 + {{> passport/header}}
  3 + <div class="content">
  4 + <p class="sms-login__msg small">你以后还可以使用手机号码 + 密码的形式登录有货哦!</p>
  5 + <div class="input-container row has-eye">
  6 + <input id="pwd" class="pwd input" type="password" placeholder="密码">
  7 + <div class="eye close" id="eye"></div>
  8 + </div>
  9 + <span id="btn-next" class="btn btn-next disable row">确定</span>
  10 + </div>
  11 +</div>
  1 +var tip = require('plugin/tip');
  2 +
  3 +var $resendBtn,
  4 + $nextBtn,
  5 + $smsCode,
  6 + mobile, area;
  7 +
  8 +var page = {
  9 + time: 3,
  10 + resendText: '重发验证码',
  11 + timerId: null,
  12 + init: function() {
  13 + this.domInit();
  14 + this.bindEvents();
  15 + this.countDown();
  16 + },
  17 +
  18 +
  19 + domInit: function() {
  20 + $resendBtn = $('#resend-sms');
  21 + $nextBtn = $('#btn-next');
  22 + $smsCode = $('#sms-code');
  23 + mobile = $('#mobile').val();
  24 + area = $('#area').val();
  25 + },
  26 +
  27 +
  28 + bindEvents: function() {
  29 + var self = this;
  30 +
  31 + $resendBtn.on('click', function() {
  32 + self.resendSMS();
  33 + });
  34 +
  35 + $smsCode.on('input', function() {
  36 + var hasVal = Boolean($.trim(this.value));
  37 +
  38 + $nextBtn.toggleClass('disable', !hasVal);
  39 + });
  40 +
  41 + $nextBtn.on('click', function() {
  42 + self.submit();
  43 + });
  44 + },
  45 +
  46 +
  47 + countDown: function() {
  48 + var self = this;
  49 + var second = this.time;
  50 +
  51 + if (this.timerId) {
  52 + return;
  53 + }
  54 +
  55 + $resendBtn.prop('disable', true);
  56 + this.timerId = setInterval(function() {
  57 + var txt = self.resendText;
  58 +
  59 + second = second - 1;
  60 +
  61 + if (second < 0) {
  62 + clearInterval(self.timerId);
  63 + self.timerId = null;
  64 + $resendBtn.prop('disable', false);
  65 + } else {
  66 + txt = second + 's';
  67 + }
  68 +
  69 + $resendBtn.text(txt);
  70 + }, 1000);
  71 +
  72 + },
  73 +
  74 +
  75 + resendSMS: function() {
  76 + var self = this;
  77 +
  78 + if ($resendBtn.prop('disable')) {
  79 + return;
  80 + }
  81 +
  82 + $.get('/passport/sms_login/token', {
  83 + area: area,
  84 + mobile: mobile,
  85 + })
  86 + .done(function() {
  87 + self.countDown();
  88 + });
  89 + },
  90 +
  91 + submit: function() {
  92 + var code = $.trim($smsCode.val());
  93 +
  94 + $.get('/passport/sms_login/check', {
  95 + code: code
  96 + })
  97 + .done(function(res) {
  98 + if (res.code === 200) {
  99 + location.href = res.redirect;
  100 + return;
  101 + }
  102 +
  103 + tip.show(res.message);
  104 + })
  105 + .fail(function() {
  106 + tip.show('出错了, 请重试');
  107 + });
  108 + }
  109 +};
  110 +
  111 +$(function() {
  112 + page.init();
  113 +});
  1 +'use strict';
  2 +
  3 +var tip = require('plugin/tip');
  4 +var api = require('./api');
  5 +
  6 +var $countrySelect,
  7 + $areaCode,
  8 + $nextBtn,
  9 + $phoneNum;
  10 +
  11 +var page = {
  12 + disableAjax: false,
  13 + init: function() {
  14 + this.domInit();
  15 + this.bindEvent();
  16 + },
  17 + domInit: function() {
  18 + $countrySelect = $('#country-select');
  19 + $areaCode = $('#area-code');
  20 + $nextBtn = $('#btn-next');
  21 + $phoneNum = $('#phone-num');
  22 + },
  23 + bindEvent: function() {
  24 + var self = this;
  25 +
  26 + $countrySelect.on('change', function() {
  27 + $areaCode.text(this.value);
  28 + });
  29 + $phoneNum.on('input', function() {
  30 + var bool = Boolean($.trim(this.value));
  31 +
  32 + $nextBtn.toggleClass('disable', !bool);
  33 + });
  34 +
  35 + $nextBtn.on('click', function() {
  36 + !self.disableAjax && self.goNext();
  37 + });
  38 + },
  39 +
  40 + // 提交按钮
  41 + goNext: function() {
  42 + var areaCode = $countrySelect.val();
  43 + var phone = $.trim($phoneNum.val());
  44 + var self = this;
  45 +
  46 + if ($nextBtn.hasClass('disable')) {
  47 + return;
  48 + }
  49 +
  50 + if (!api.phoneRegx[areaCode].test(phone)) {
  51 + tip.show('手机号码格式不正确, 请重新输入');
  52 + return;
  53 + }
  54 +
  55 + $.get('/passport/sms_login/token', {
  56 + area: areaCode.replace('+', ''),
  57 + mobile: phone
  58 + })
  59 + .done(function(data) {
  60 + if (data.code === 200) {
  61 + location.href = data.redirect;
  62 + } else {
  63 + tip.show(data.message);
  64 + }
  65 + })
  66 + .fail(function() {
  67 + tip.show('出错了, 请重试');
  68 + })
  69 + .always(function() {
  70 + self.disableAjax = false;
  71 + });
  72 +
  73 +
  74 +
  75 + }
  76 +};
  77 +
  78 +$(function() {
  79 + page.init();
  80 +});
  1 +var $eyeBtn,
  2 + $pwd,
  3 + $nextBtn;
  4 +
  5 +var page = {
  6 + init: function() {
  7 + this.domInit();
  8 + this.bindEvent();
  9 + },
  10 + domInit: function() {
  11 + $eyeBtn = $('#eye');
  12 + $pwd = $('#pwd');
  13 + $nextBtn = $('#btn-next');
  14 + },
  15 + bindEvent: function() { },
  16 +};
  17 +
  18 +$(function() {
  19 + page.init();
  20 +});
@@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
6 @import "layout/swiper"; 6 @import "layout/swiper";
7 @import "layout/header"; 7 @import "layout/header";
8 @import "layout/footer"; 8 @import "layout/footer";
  9 +@import "layout/form";
9 @import "common/index"; 10 @import "common/index";
10 @import "channel/index"; 11 @import "channel/index";
11 @import "product/index"; 12 @import "product/index";
  1 +.input-group {
  2 + display: table;
  3 +
  4 + .input {
  5 + width: 100%;
  6 + }
  7 +
  8 + .input,
  9 + .input-addon {
  10 + display: table-cell;
  11 + vertical-align: middle;
  12 + }
  13 +}
1 @import "common"; 1 @import "common";
2 @import "register"; 2 @import "register";
3 @import "login"; 3 @import "login";
  4 +@import "sms_login";
4 @import "back"; 5 @import "back";
5 @import "code"; 6 @import "code";
6 @import "bind"; 7 @import "bind";
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 text-align: left; 20 text-align: left;
21 font-size: 16PX; 21 font-size: 16PX;
22 22
23 - .go-register { 23 + .sms-login {
24 text-decoration: underline; 24 text-decoration: underline;
25 color: #858585; 25 color: #858585;
26 } 26 }
@@ -82,11 +82,19 @@ @@ -82,11 +82,19 @@
82 background-color: #333; 82 background-color: #333;
83 border: none; 83 border: none;
84 border-radius: 20PX; 84 border-radius: 20PX;
85 - margin: 0 auto; 85 + margin: 0 auto 28px;
86 font-size: 16PX; 86 font-size: 16PX;
87 color: #d8d8d8; 87 color: #d8d8d8;
88 } 88 }
89 89
  90 + .go-register {
  91 + color: #858585;
  92 +
  93 + a {
  94 + color: inherit;
  95 + }
  96 + }
  97 +
90 .login-tip { 98 .login-tip {
91 font-size: 16PX; 99 font-size: 16PX;
92 position: relative; 100 position: relative;
  1 +.sms-login {
  2 + .sms-input {
  3 + margin-top: 60px;
  4 + }
  5 + #resend-sms {
  6 + display: block;
  7 + background-color: transparent;
  8 + width: 190px;
  9 + height: 52px;
  10 + border: 1px solid #36a74c;
  11 + border-radius: 26px;
  12 + font-size: 20px;
  13 + line-height: 1;
  14 + color: #36a74c;
  15 + }
  16 +
  17 + button {
  18 + border: none;
  19 + }
  20 +}
  21 +
  22 +.sms-login__msg {
  23 + font-size: 28px;
  24 + color: #fff;
  25 + margin-bottom: 20px;
  26 +
  27 + .tel {
  28 + color: #41cbe7;
  29 + }
  30 +
  31 + &.small {
  32 + font-size: 22px;
  33 + color: #858585;
  34 + }
  35 +}
@@ -39,6 +39,11 @@ module.exports = { @@ -39,6 +39,11 @@ module.exports = {
39 path: path.join(__dirname, 'bundle'), // absolute path 39 path: path.join(__dirname, 'bundle'), // absolute path
40 filename: '[name].js' 40 filename: '[name].js'
41 }, 41 },
  42 + resolve: {
  43 + alias: {
  44 + plugin: path.join(__dirname, 'js/plugin')
  45 + }
  46 + },
42 plugins: [ 47 plugins: [
43 new webpack.optimize.OccurenceOrderPlugin(), 48 new webpack.optimize.OccurenceOrderPlugin(),
44 new webpack.optimize.CommonsChunkPlugin({ 49 new webpack.optimize.CommonsChunkPlugin({