折腾毕设记(二)—— 用户注册登录

前言

上回讲述了我搭建好了部分开发环境,从这一章开始,就要动手写代码了。

一个像样的网站要有注册登录功能。所以今天讲一讲,我是如何设计开发毕设的登录注册的,效果如下方所示


那么,整理一下本次的开发目标。

开发目标

  1. 打开毕设网站就显示登录页面(vue-router)
  2. 注册和登陆的页面设计(采用element-ui)
  3. 登录页面和注册页面之间的路由跳转(vue-router)
  4. 注册页面的动态设计和与服务器的通讯(vue的数据绑定,axios调用api,epress作为服务器,mongodb作为数据库)

1.打开毕设网站就显示登录页面

其实登录页面的显示方式有很多种,我目前能想到以下三种方式:

  1. 访问根目录,先进入Home主页,然后点击登陆按钮弹出一个dialog的表单,如:腾讯视频
  2. 访问根目录,先进入Home页面,然后点击登陆按钮跳转到另一个登录页面,如:bilibili
  3. 访问根目录,直接进入登录页面,如:QQ邮箱
本项目图省事儿,方式3正合我意。接下来讲解方式3。

首先,该模板中,express已配置好了以下代码,坐标src/server/index.js

//项目中的静态文件放在根目录的views文件夹下
app.use(express.static(path.join(__dirname, 'views')))
//当有人访问根目录的时候,把views文件夹中的index.html给他看
app.get('/', function (req, res) {    res.sendFile('./views/index.html')})
复制代码

接下来,再去看看这个index.html,坐标src/server/views/index.html



  "utf-8">  
不要关掉我

  
"app">
复制代码

这里甚至没有看到任何script标签,只有一个body,但有这句话:built files will be auto injected!打包文件将会自动插入!

打包啥?打包src/client文件夹下的所有文件:webpack会从入口文件开始,也就是从src/client/index.js开始,顺藤摸瓜,把所有涉及到的文件,打包成一个JavaScript,插入到src/server/views/index.html中。

继续前进,去看看这个打包的入口文件,坐标src/client/index.js

import ElementUi from 'element-ui';            //引入element-ui组件库
import 'element-ui/lib/theme-chalk/index.css'  //引入element-ui样式文件
import Vue from 'vue'                          //引入Vue
import App from './App'                        //引入App.vue组件,作为视图入口
import router from './router/index'            //引入配置的路由文件
import store from './store/store'              //引入状态管理器
import axios from 'axios'                      //引入axios可请求HTTP服务
Vue.use(ElementUi)                             //全局注册element-ui组件库,在所有组件都能用
Vue.config.debug = true
Vue.config.productionTip = false
Vue.prototype.$axios = axios                   //将axios设置为全局属性,所有组件可this.$axios
//定义一个vue实例
new Vue({
  el: '#app',
  router: router,
  store: store,
  template: '',
  components: { App }
})
复制代码

在vue实例中,el对应的值'#app',就是本实例要控制的元素

src/server/views/index.html中的

template就是本组件要渲染的模板,components属性注册这个实例中所需要的组件,{App}其实就是{App:App}的简写,{App:App}这个对象中,属性就是组件名,值就是组件对象,这个组件对象在上方我们已经通过import App from './App'导入。所以如果愿意,也可以写成{'my-app':App},那么template中也要对应改成

不过只有在实例中注册了components属性,template模板中的才可以生效。

也就是说,这个打包的入口文件,除了引入必要的文件,只显示App.vue组件

接下来就去App.vue看看。坐标src/client/App.vue



复制代码

如果忽略样式,忽略script中的各种空对象,其实该文件就可以看成是

复制代码

即路由视图!也就是说,路由到哪儿,App就显示啥!

现在想要的效果是刚刚的方式3:访问根目录,直接进入登录页面。

也就是说只要用户访问了'/'根目录,就显示包含登录页面的视图。比如说,只显示一个登录组件Login.vue,那么只需要设置,'/'对应的路由视图为Login.vue即可。

来,咱去vue-router路由看看,坐标src/client/router/index.js

import Vue from 'vue'                      //导入vue
import Router from 'vue-router'            //导入vue-router
// 导入相应的子组件
import Login from './../components/Login'

Vue.use(Router)                            //全局注册路由,所有组件可访问this.$router
var router = new Router({
  mode: 'history',
  routes: [
    {
       name: 'login',
       path: '/',
       component: Login
     }
  ]
})
export default router复制代码

我尝试用浅薄的理解来解释一下这个vue-router的使用,想象一下哆啦A梦取三个很多任意门,任意门A可以去静香家,任意门B可以去学校,任意门C可以去空地。

大雄可以自由选择进哪个门,想去哪儿就走进哪个门。如果这个时候,大雄想去中国,那走这三个门是没有用的,必须要哆啦A梦再拿出一个可以去中国的任意门。只有哆啦A梦拿出了这个门,大雄才有机会去中国。

上述例子中,哆啦A梦就是写代码的我,任意门就是路由,大雄就是用户,大雄走进哪个门就是使用了哪个路由。

显然,现在要做的,是给大雄拿出一个可以进入登录页面的任意门。也就是在vue-router中注册一个可以访问登录组件的路由。

设置一个路由,只要编辑一个json对象,加到 src/client/router/index.js中的 Router实例中的 routers数组中即可。

路由至登录组件的json如下

{
    name:'login',
    path:'/',
    component: Login 
}复制代码

name表示给该路由起个名字,path表示请求路径,比如在本地访问的话,就是localhost:3000/homecomponent表示该路由对应显示的组件,这里对应的是我们src/client/components/Login.vue组件,所以我们需要将Login.vue组件引入src/client/router/index.js路由文件中。

至此,当我们访问项目根目录,便会直接显示Login.vue组件!

2.注册和登录的页面设计

然而,在src/client/components/下并没有Login.vue文件。所以需要该目录下新建一个Login.vue,然后设计制作它。

其实页面设计并不难,尤其是使用了element-ui组件库,样式都是别人封装好的。

难的是动态显示,比如在注册页面,当学号格式符合要求的时候,文本框后面动态地显示一个提示正确的icon,同时,下方的文本框解除禁用状态。我将会在这一个部分的后半段讲解我是如何实现动态显示的。

那么首先,先搭出登录页面的架子,坐标src/client/components/Login.vue



复制代码

效果如下图

那么接下来,希望点击注册按钮之后,跳转到显示注册组件的页面中去!可还没有注册组件呢,总要先做出来吧?

那么在src/client/components/下,新建一个Register.vue组件,

接下来,搭出注册页面的架子,坐标src/client/components/Register.vue






复制代码

效果如下图


至此,已经成功通过element-ui搭建好登录和注册的两个页面架子!

3.登录页面和注册页面之间的路由跳转

现在两个页面架子已经有了,那么接着上面的问题,希望从登录界面,点击注册按钮之后,跳转注册页面去,这么做呢?

可以这么理解,当访问'/register'路径时,App.vue就显示Register.vue组件,于是就是要注册一个path'/register'componentRegister的路由,

拿出这个json任意门:

{
    name:'register',
    path:'/register',
    component: Register
}
复制代码

如何让“大雄”走进这个门呢?

当我们点击注册的时候,绑定一个点击事件,触发一个函数,在这个函数内,跳转路由。

坐标src/client/components/Login.vue



复制代码

this.$router.push('/register')中,this.$router就是访问了全局路由,push方法可以实现路由跳转,所带的一个参数,可以使注册路由中的name,也可以是path等等,具体可以去看官方文档,非常详细。

至此,点击注册后,页面将会由根目录下的登录页面,跳转至注册页面。

4.注册页面的动态设计和与服务端的通讯

本小节涵盖内容较多,首先是注册页面的动态效果。

单以学号和密码这两个输入框为例,其他输入框的动态效果以此类推。

  1. 初始状态,仅学号(即注册账号)的输入框为可输入状态,密码输入框为禁用状态。
  2. 一旦输入学号满8位,或者焦点离开了学号输入框,就做以下两个条件判断:
    1. 输入的学号是否符合'JAPXXXXX'的格式。
    2. 输入的学号是否被注册过。
  3. 若输入学号不符合条件,则在学号输入框后方显示错误信息。
  4. 若输入学号符合条件,则在学号输入框后方显示正确图标。解除密码输入框的禁用状态。

整理好以上逻辑,下面一步一步去用vue实现它们。

1.初始状态,仅学号的输入框为可输入状态,密码输入框为禁用状态。

这一节的坐标都在这儿:src/client/components/Register.vue




复制代码

在密码输入框中的:disabled='true'即指定该文本框为禁用状态,用boolean值控制禁用状态的开启和解除。因此可以在后面通过控制boolean值来控制禁用状态。

同时,将两个输入框双向绑定到data的数据中了,即v-model,当文本框中输入数据时,data中的数据会实时更新。

2.一旦输入学号满8位,或者焦点离开了学号输入框,做条件判断。

这里有两个判断:

  1. 输入的学号是否符合'JAPXXXXX'的格式。
  2. 输入的学号是否被注册过。

此时就出现几个问题,

  1. 如何判断焦点(也就是光标)是否在学号输入框内。
  2. 如何判断学号是否被注册过。

首先解决问题1,我的方法是在学号输入框绑定焦点移入移出事件,并在data中定义foucsIn来记录焦点所在位置。如下:

"registerForm.id"
     @blur="foucsIn=''"
     @focus="foucsIn='id'">
复制代码

当焦点移入学号输入框时,foucsIn的值变为id。让移出时,foucsIn为空。

这样,我们就可以通过访问foucsIn判断焦点在不在学号输入框内了,问题1可以暂时告一段落。

然后是问题二,如何判断学号是否被注册过。

整理一下思路,当学号符合格式,就可以把学号发送至expressexpress拿到我们的学号去mongoDB里面查询是否已有记录。

若无记录,则响应前端可以注册,若有记录,则响应前端该账号已被注册。

道理是这么个道理,如何实现呢?只要在express中,封装一个接口,验证学号是否被注册过,再在前端中使用这个接口,接收响应值。

来吧,去造造这个接口,坐标src/server/index.js

//导入User的模型
import User from './../models/User'    ...
app.post('/register-id',async (req,res)=>{
    try {
        //查询数据库
        let user = await User.findOne({uid:req.body.id}).exec();
        if(user){
            return res.json({err:'该学号已被注册'});
        }
        return res.json({msg:'ok'});
    } catch (error) {
        throw error
    }
})

    ...复制代码

我以我的理解来解释一下以上代码:

当服务器实例app,监听到有人访问'/register-id'接口时,触发一个回调函数。

这个回调函数通过async/await异步处理,需要一个try catch来捕获错误。

回调函数中有两个参数,

第一个参数req是客户端请求'/register-id'接口时,生成的对象。

第二个参数res是服务端在在响应客户端时需要返回的对象。

而客户端发送来的数据,便会存在reqbody中,为了获取req中的body,需要有中间件body-parser解析所有请求,不过本项目模板以为我们配置好:

app.use(bodyParser.json())app.use(bodyParser.urlencoded({ extended: false }))复制代码

继续,我们在try的作用域中,我们先通过客户端传来的学号req.body.id查询一下数据库,若数据库中无记录,则userundifine,若数据库中已有该学号,则user为一个用户数据对象。

因此,判断user,若user不为undifine,即已存在该用户,则让res返回一个带errjson文件通知客户端,该学号已被注册。

否则,随便响应结束本次访问,不带err即可。这样客户端就知道,该学号没被注册过了。

那么,导入的User模型是什么?

我再以我的理解解释一下它,它就类似一个容器,就像一个空表格,已经给你定好了表头,但是内容都是空的,而这个容器模型是通过mongoose模块定义的。

我们去看看这个模型,坐标src/server/models/User.js

import mongoose from 'mongoose';
const userSchema = new mongoose.Schema({
  id: {type: String, unique: true, required: true, trim: true },
  realname: { type: String, required: true, trim: true },
  password: { type: String, required: true },
  post_box: { type: String },
  create_time: { type: Date, default: Date.now() },
  friends: { type: Array },
  friend_req: { type: Array },
  admin: { type: Boolean, default: false },
  token: { type: String }
});

export default mongoose.model('User', userSchema);
复制代码

一个用户模型中,定义了可以储存学号,真实姓名,密码(通过sha1加密),邮箱,创建时间,好友,要有请求,是否为管理员,和token的空间。

并且规定了他们的变量类型和要求。

而模型(column)会对应的去找对应的去找mongoDB中的集合,然后操作集合(table)中的文档(row)。

等到注册的时候,我们就要通过客户端发来的表单,结合这个模型,往mongoDB中加一个文档,就注册完成啦。

mongoose的科普到次,现在接口已经做好了,现在来看看如何使用:

我在computed中定义一个计算属性ifIdCorrect监听文本框中的数据变化:

computed:{
    if(this.registerForm.id.length>=8||this.foucsIn!='id'){//当输入满8位,或焦点离开id
        if(!/^JAP\d{5}$/.test(this.registerForm.id)){//判断学号是否符合'JAPXXXXX'
            return false;
        }
        this.$axios.post('/api/register-id',{id:this.registerForm.id})//调用接口判断是否被注册过
        .then(res=>{
            if(res.data.err){
                return false;
            }
        })
        return true;
    }}复制代码

可通过this.$axios访问全局属性axios,通过post的请求方式访问服务端接口。并把参数id,也就是学号,以json形式传给服务端。

请求后,axios会返回一个promis对象,在.then()中,获得服务端返回的res对象,在resdata中,就是服务端返回的json对象。

根据刚刚造的API接口,如果这个对象中有err,那么就被注册过了。

至此,可以通过计算属性idIdCorrect的值来判断学号是否符合条件。

3.若输入学号不符合条件,则在学号输入框后方显示错误信息。

这一环节需要考虑以下几点:

  1. 什么时候显示错误信息。(when)
  2. 用什么来作为错误信息的载体。(how)
  3. 错误信息的内容是什么?(what)

先整理这三个问题,再着手写代码。

首先,当判断学号是否符合要求后,若学号格式不对,或者已经被注册过,则显示错误信息。否则不显示。

其次,在data中定义一个errMsg的对象,专门储存对应输入框的错误信息。此处应该在对象中加入id属性,内容为空。

最后,在学号输入框后方加入一个红色的标签,标签的内容为errMsg中的id的值。

将以上思路整理成代码:

data中定义errMsg

data(){
    return{
        registerForm:{
            id:'',
            ....
        },
        errMsg:{
            id:''
        },
        ...
    }  
}复制代码

在computed中控制errMsg.id的内容

ifIdCorrect(){
    if(this.registerForm.id.length<8&&this.foucsIn==='id'){//当焦点在id,且输入值未满8位的时候
        this.errMsg.id=''//清空错误数据
        return false
    }
    if(this.registerForm.id.length>=8||this.foucsIn!='id'){//当需要执行判断的时候
        if(!/^JAP\d{5}$/.test(this.registerForm.id)){//若学号不符合格式
            this.errMsg.id='学号格式错误'; //让错误信息为,学号格式错误
            return false
        }
        this.$axios.post('/api/register-id',{id:this.registerForm.id})
        .then(res=>{
            if(res.data.err){//若请求接口后返回了错误信息
                this.errMsg.id = res.data.err;//让错误信息为,返回的错误信息(已被注册)
                return false;
            }
            this.errMsg.id=''//否则,学号符合要求,清空错误数据。
        })
    }
    return this.errMsg.id==''?true:false//运行至此,若错误信息为空,则ifIdCorrect为true
}
复制代码

在template中,往学号输入框后方加入显示错误信息的红色标签。

'学号'>
    "12">
        "registerForm.id"
             @blur="foucsIn=''"
             @focus="foucsIn='id'">
        
    
    "12">
        "errMsg.id!=''"
             type="danger"
             v-text="errMsg.id">
        
    

复制代码

如果errMsg.id不为空,则显示错误信息,否则不显示。

至此,错误信息提示解决完毕。

4.若输入学号符合条件,则在学号输入框后方显示正确图标。解除密码输入框的禁用状态。

此处需要考虑:

  1. 显示正确图标(解除密码输入框禁用状态)的条件是什么。(when)
  2. 如何显示正确图标。(how)

还是像刚刚一样,先整理思路:

首先,计算属性ifIdCorrect的值为true的话,说明学号符合条件。

其次,用element-ui自带的icon可以作为提示。

最后,密码框禁用状态有boolean来控制,也就是说可以直接用ifIdCorrect来控制。

将上述思路整理成代码:

"20">
    '学号'>
        "12">
            "registerForm.id"
                 @blur="foucsIn=''"
                 @focus="foucsIn='id'">
            
        
        "12">
            "errMsg.id!=''"
                 type="danger"
                 v-text="errMsg.id">
            
            "ifIdCorrect==true"
                 class="el-icon-success color-green">
            
        
    

'密码'>
    'registerForm.password'
         :disabled="!(ifIdCorrect===true"
         type='password'
         @blur="foucsIn=''"
         @focus="foucsIn='password';errMsg.password=''">
    
复制代码

后续输入框的动态设计如法炮制,便显示为本章开头gif效果(因为代码做了局部改善,所以和原来的有点不一样,请见谅!)

至此,本小节内容搞定。すごいですね!

感谢阅读,下期不定期更新!まだ!


你可能感兴趣的:(折腾毕设记(二)—— 用户注册登录)