nodejs+vue實現登錄界面功能(一)


項目描述:一開始進入登錄界面,只有登錄成功才可以跳轉到主頁面,已注冊但是忘記密碼的進入忘記密碼頁面,找回密碼后進入登錄界面。

技術選型:nodejs+vue+stylus

界面效果:

  • 切換登錄方式
  • 手機合法檢查
  • 倒計時效果
  • 切換顯示或隱藏密碼
  • 前台驗證提示

前后台交互功能

  • 動態一次性圖形驗證碼
  • 動態一次性短信驗證碼
  • 短信登錄
  • 密碼登錄
  • 獲取用戶信息,實現自動登錄
  • 退出登錄

 

技術點講解

  • 切換登錄方式

使用:class綁定樣式,如果沒有該樣式,標簽設置為display:none

 

  • 手機的合法檢查

使用正則,規則為以1開頭,共11位數字。注意這里將方法寫在computed中,當用戶輸入的phone改變時rightPhone才重新計算,減少緩存。

computed: {
        rightPhone () {
          return /^1\d{10}$/.test(this.phone)
        }
      },

 

  • 倒計時效果

當輸入手機號后,如果手機號輸入正確,“獲取驗證碼”由灰色顯示為黑色(color #ccc-> #000),可以按下按鈕(:disabled = !rightPhone),發送請求(getCode),同時倒數顯示,“獲取驗證碼”字樣隱藏。

<template>
....    
        <input type="tel" maxlength="11" placeholder="手機號" v-model="phone">
            <button :disabled="!rightPhone" class="get_verification"
                    :class="{right_phone: rightPhone}" @click.prevent="getCode">
              {{computeTime>0 ? `已發送(${computeTime}s)` : '獲取驗證碼'}}
            </button>
....
</template>
<script>
.....
data(){
    return{
        computeTime:0;
        }
}

methos:{
async getCode () {
          // 如果當前沒有計時
          if(!this.computeTime) {
            // 啟動倒計時
            this.computeTime = 60
            this.intervalId = setInterval(() => {
              this.computeTime--
              if(this.computeTime<=0) {
                // 停止計時
                clearInterval(this.intervalId)
              }
            }, 1000)
......

 

  • 切換顯示或密碼隱藏

兩個input,類型分別為type=password,text;使用v-if,v-else綁定showPwd(一個布爾值)決定顯示哪一個。

<template>
...
<section class="login_verification">
              <input type="text" maxlength="8" placeholder="密碼" v-if="showPwd" v-model="pwd">
              <input type="password" maxlength="8" placeholder="密碼" v-else v-model="pwd">
              <div class="switch_button" :class="showPwd?'on':'off'" @click="showPwd=!showPwd">
                <div class="switch_circle" :class="{right: showPwd}"></div>
                <span class="switch_text">{{showPwd ? '' : ''}}</span>
              </div>
</section>
...
</template>
<script>
export default {
      name: "login",
      data () {
        return {
          showPwd: false, // 是否顯示密碼
          pwd:'',
                }
        }
.....
}

 

  • 前台驗證提示

用戶每次點擊獲取圖形驗證碼時都會不同,即img中src不能相同,可以在請求地址后面加上請求時間Date.now()

<template>
...
<section class="login_message">
              <input type="text" maxlength="11" placeholder="驗證碼" v-model="captcha">
              <img class="get_verification" src="http://localhost:4000/captcha" alt="captcha"
                   @click="getCaptcha" ref="captcha">
</section>

<script>
.....
methods:{
    // 獲取一個新的圖片驗證碼
        getCaptcha () {
          // 每次指定的src要不一樣
          this.$refs.captcha.src = 'http://localhost:4000/captcha?time='+Date.now()
        }
}
}

 

  • 點擊登錄提交表單

 這里分為短信登錄和用戶密碼登錄兩個部分,都是提交表單,即為post請求,短信登錄中提交的是電話號碼和驗證碼,用戶密碼登錄提交的是手機號、密碼和圖形驗證碼。

開始先安裝axios

接着封裝ajax請求,返回promise對象,

import axios from 'axios'
export default function ajax (url, data={}, type='GET') {

  return new Promise(function (resolve, reject) {
    // 執行異步ajax請求
    let promise
    if (type === 'GET') {
      // 准備url query參數數據
      let dataStr = '' //數據拼接字符串
      Object.keys(data).forEach(key => {
        dataStr += key + '=' + data[key] + '&'
      })
      if (dataStr !== '') {
        dataStr = dataStr.substring(0, dataStr.lastIndexOf('&'))
        url = url + '?' + dataStr
      }
      // 發送get請求
      promise = axios.get(url)
    } else {
      // 發送post請求
      promise = axios.post(url, data)
    }
    promise.then(function (response) {
      // 成功了調用resolve()
      resolve(response.data)
    }).catch(function (error) {
      //失敗了調用reject()
      reject(error)
    })
  })
}

根據提供的api編寫請求地址

import ajax from './ajax'
//設置跨域,跨域地址為http://localhost:4000
import apiConfig from '../../config/api.config.js'
axios.defaults.baseURL=apiConfig.baseURL

// 用戶名密碼登陸
export const reqPwdLogin = ({name, pwd, captcha}) => ajax('/login_pwd', {name, pwd, captcha}, 'POST')
// 發送短信驗證碼
export const reqSendCode = (phone) => ajax('/sendcode', {phone})
// 手機號驗證碼登陸
export const reqSmsLogin = (phone, code) => ajax('/login_sms', {phone, code}, 'POST')
// 根據會話獲取用戶信息
export const reqUserInfo = () => ajax('/userinfo')
// 用戶登出
export const reqLogout = () => ajax('/logout')

 

這里先配置一下跨域,假設跨域地址為http://localhost:4000,以下同時配置開發環境和生產環境的跨域,這樣上線后無需更改也可以請求接口

配置目錄 /config/index.js

proxyTable: {
      '/apis':{
        target: 'http://localhost:4000/',  // 后台api
        changeOrigin: true,  //是否跨域
        // secure: true,
        pathRewrite: {
          '^/apis': ''   //需要rewrite的,
        }
      }
    },

在config文件里新建一個js文件api.config.js

//判斷是否是生產環境
var isPro = process.env.NODE_ENV === 'production' //process.env.NODE_ENV用於區分是生產環境還是開發環境
//根據環境不同導出不同的baseURL
module.exports = {
    baseURL: isPro ? 'http://localhost:4000/' : '/apis'
}

在axios的默認實例有一個baseURL的屬性,配置了baseURL之后,訪問接口時就會自動帶上,這里對應api地址請求中的

//設置跨域,跨域地址為http://localhost:4000
import apiConfig from '../../config/api.config.js'
axios.defaults.baseURL=apiConfig.baseURL

寫好接口請求和配置好跨域后,我們回到登錄請求方面。

 // 異步登陸
        async login () {
          let result
          // 前台表單驗證
          if(this.loginWay) {  // 短信登陸
            const {rightPhone, phone, code} = this
            if(!this.rightPhone) {
              // 手機號不正確
              this.showAlert('手機號不正確')
              return
            } else if(!/^\d{6}$/.test(code)) {
              // 驗證必須是6位數字
              this.showAlert('驗證必須是6位數字')
              return
            }
            // 發送ajax請求短信登陸
            result = await reqSmsLogin(phone, code)

          } else {// 密碼登陸
            const {name, pwd, captcha} = this
            if(!this.name) {
              // 用戶名必須指定
              this.showAlert('用戶名必須指定')
              return
            } else if(!this.pwd) {
              // 密碼必須指定
              this.showAlert('密碼必須指定')
              return
            } else if(!this.captcha) {
              // 驗證碼必須指定
              this.showAlert('驗證碼必須指定')
              return
            }
            // 發送ajax請求密碼登陸
            result = await reqPwdLogin({name, pwd, captcha})
          }

          // 停止計時
          if(this.computeTime) {
            this.computeTime = 0
            clearInterval(this.intervalId)
            this.intervalId = undefined
          }

          // 根據結果數據處理
          if(result.code===0) {
            const user = result.data
            // 將user保存到vuex的state
            this.$store.dispatch('recordUser', user)
            // 跳轉首頁
            this.$router.replace('/home')
          } else {
            // 顯示新的圖片驗證碼
            this.getCaptcha()
            // 顯示警告提示
            const msg = result.msg
            this.showAlert(msg)
          }
        },

 

此時我們先暫停以下前台的編寫,使用nodejs來編寫后台響應。

注意:以下講到的模塊的參數寫法具體參考www.npmjs.com中自己搜索模塊,這里不會詳細講解每個參數是什么意思。

 在這里我使用的是nodejs+express+mongoose來搭建。首先確定你的電腦上已經配置好nodejs,MongoDB,express。

使用express新建項目server

express -e server
  • 完成用戶密碼方式登錄

首先考慮獲取驗證碼,在這里我們使用svg-captcha

router.get('/captcha', function (req, res) {
  var captcha = svgCaptcha.create({
    ignoreChars: '0o1l',
    noise: 2,//產生線數
    color: true
  });
  req.session.captcha = captcha.text.toLowerCase();
  console.log(req.session.captcha)
  /*res.type('svg');
  res.status(200).send(captcha.data);*/
  res.type('svg');
  res.send(captcha.data)
});

然后我們再連接數據庫。注意這里登陸總共存在數據庫中的參數有手機號,用戶名,密碼三個。

/*
包含n個能操作mongodb數據庫集合的model的模塊
1. 連接數據庫
  1.1. 引入mongoose
  1.2. 連接指定數據庫(URL只有數據庫是變化的)
  1.3. 獲取連接對象
  1.4. 綁定連接完成的監聽(用來提示連接成功)
2. 定義對應特定集合的Model
  2.1. 字義Schema(描述文檔結構)
  2.2. 定義Model(與集合對應, 可以操作集合)
3. 向外暴露獲取Model的方法
 */
// 1. 連接數據庫
const mongoose = require('mongoose')
mongoose.connect('mongodb://localhost:27017/order_manager')
const conn = mongoose.connection
conn.on('connected', function () {
  console.log('數據庫連接成功!')
})

// 2. 得到對應特定集合的Model: UserModel
const userSchema = mongoose.Schema({
  // 用戶名
  'name': {type: String},
  // 密碼
  'pwd': {type: String},
  // 類型
  'phone': {'type': String}
})
UserModel = mongoose.model('user', userSchema)

// 3. 向外暴露
module.exports = {
  getModel(name) {
    return mongoose.model(name)
  }
}

接着我們來驗證用戶提交的用戶密碼登錄的信息。

*
密碼登陸
 */
router.post('/login_pwd', function (req, res) {
  const name = req.body.name
  const pwd = md5(req.body.pwd)
  const captcha = req.body.captcha.toLowerCase()
  console.log('/login_pwd', name, pwd, captcha, req.session)

  // 可以對用戶名/密碼格式進行檢查, 如果非法, 返回提示信息
  if(captcha!==req.session.captcha) {
    return res.send({code: 1, msg: '驗證碼錯誤'})
  }
  // 刪除保存的驗證碼
  delete req.session.captcha

  UserModel.findOne({name}, function (err, user) {
    if (user) {
      console.log('findUser', user)
      if (user.pwd !== pwd) {
        res.send({code: 1, msg: '用戶名或密碼不正確!'})
      } else {
        req.session.userid = user._id
        res.send({code: 0, data: {_id: user._id, name: user.name, phone: user.phone}})
      }
    } else {
      const userModel = new UserModel({name, pwd})
      userModel.save(function (err, user) {
        // 向瀏覽器端返回cookie(key=value)
        // res.cookie('userid', user._id, {maxAge: 1000*60*60*24*7})
        req.session.userid = user._id
        const data = {_id: user._id, name: user.name}
        // 3.2. 返回數據(新的user)
        res.send({code: 0, data})
      })
    }
  })
})

 

  • 完成用戶手機方式登錄

首先在網上雲之訊中注冊新用戶(這里我是隨便選的,主要是雲之訊新用戶免費送100條短信測試,所以我就注冊了,其他的也可以)。注冊成功后你可以獲得自己的api接口對接(APPID,ACCOUNT,TOKEN,REST URL)。

這里我們先編寫生成隨機驗證碼的函數。

/*
 生成指定長度的隨機數
 */
function randomCode(length) {
    var chars = ['0','1','2','3','4','5','6','7','8','9'];
    var result = ""; //統一改名: alt + shift + R
    for(var i = 0; i < length ; i ++) {
        var index = Math.ceil(Math.random()*9);
        result += chars[index];
    }
    return result;
}
// console.log(randomCode(6));
exports.randomCode = randomCode;

 

向指定的號碼發送指定的驗證碼

/*
向指定號碼發送指定驗證碼
 */
function sendCode(phone, code, callback) {
    var ACCOUNT_SID = '自己的用戶sid'
    var AUTH_TOKEN = '鑒權密鑰';
    var Rest_URL = '請求地址';
    var AppID = '應用ID';
    //1. 准備請求url
    /*
     1.使用MD5加密(賬戶Id + 賬戶授權令牌 + 時間戳)。其中賬戶Id和賬戶授權令牌根據url的驗證級別對應主賬戶。
     時間戳是當前系統時間,格式"yyyyMMddHHmmss"。時間戳有效時間為24小時,如:20140416142030
     2.SigParameter參數需要大寫,如不能寫成sig=abcdefg而應該寫成sig=ABCDEFG
     */
    var sigParameter = '';
    var time = moment().format('YYYYMMDDHHmmss');
    sigParameter = md5(ACCOUNT_SID+AUTH_TOKEN+time);
    var url = Rest_URL+'/2019-07-12/Accounts/'+ACCOUNT_SID+'/SMS/TemplateSMS?sig='+sigParameter;

    //2. 准備請求體
    var body = {
        to : phone,
        appId : AppID,
        templateId : '1',
        "datas":[code,"1"]
    }
    //body = JSON.stringify(body);

    //3. 准備請求頭
    /*
     1.使用Base64編碼(賬戶Id + 冒號 + 時間戳)其中賬戶Id根據url的驗證級別對應主賬戶
     2.冒號為英文冒號
     3.時間戳是當前系統時間,格式"yyyyMMddHHmmss",需與SigParameter中時間戳相同。
     */
    var authorization = ACCOUNT_SID + ':' + time;
    authorization = Base64.encode(authorization);
    var headers = {
        'Accept' :'application/json',
        'Content-Type' :'application/json;charset=utf-8',
        'Content-Length': JSON.stringify(body).length+'',
        'Authorization' : authorization
    }

    //4. 發送請求, 並得到返回的結果, 調用callback
    // callback(true);
    request({
        method : 'POST',
        url : url,
        headers : headers,
        body : body,
        json : true
    }, function (error, response, body) {
        console.log(error, response, body);
        // callback(body.statusCode==='0');
        callback(true);
    });
}
exports.sendCode = sendCode;

最后我們就可以編寫手機號短信驗證碼登錄的路由請求。

/*
發送驗證碼短信
*/
router.get('/sendcode', function (req, res, next) {
  //1. 獲取請求參數數據
  var phone = req.query.phone;
  //2. 處理數據
  //生成驗證碼(6位隨機數)
  var code = sms_util.randomCode(6);
  //發送給指定的手機號
  console.log(`向${phone}發送驗證碼短信: ${code}`);
  sms_util.sendCode(phone, code, function (success) {//success表示是否成功
    if (success) {
      users[phone] = code
      console.log('保存驗證碼: ', phone, code)
      res.send({"code": 0})
    } else {
      //3. 返回響應數據
      res.send({"code": 1, msg: '短信驗證碼發送失敗'})
    }
  })
})

/*
短信登陸
*/
router.post('/login_sms', function (req, res, next) {
  var phone = req.body.phone;
  var code = req.body.code;
  console.log('/login_sms', phone, code);
  if (users[phone] != code) {
    res.send({code: 1, msg: '手機號或驗證碼不正確'});
    return;
  }
  //刪除保存的code
  delete users[phone];


  UserModel.findOne({phone}, function (err, user) {
    if (user) {
      req.session.userid = user._id
      res.send({code: 0, data: user})
    } else {
      //存儲數據
      const userModel = new UserModel({phone})
      userModel.save(function (err, user) {
        req.session.userid = user._id
        res.send({code: 0, data: user})
      })
    }
  })

})

 

由於篇幅太長了,我分兩篇寫,要看后續,請看nodejs+vue實現登錄界面功能(二)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM