项目描述:一开始进入登录界面,只有登录成功才可以跳转到主页面,已注册但是忘记密码的进入忘记密码页面,找回密码后进入登录界面。
技术选型: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实现登录界面功能(二)