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实现登录界面功能(二)