項目描述:一開始進入登錄界面,只有登錄成功才可以跳轉到主頁面,已注冊但是忘記密碼的進入忘記密碼頁面,找回密碼后進入登錄界面。
技術選型: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實現登錄界面功能(二)