美文网首页
实现Vue+Element-ui登录

实现Vue+Element-ui登录

作者: Memory_2e2e | 来源:发表于2020-03-31 21:22 被阅读0次

    在上一章创建Vue项目之后,实现登录功能。

    完成之后,整个项目的大致结构如下:


    vuep.png

    1.首先修改入口页面App.vue,修改之后如下

    <template>
      <div id="app">
        <router-view/> 
        <!-- router-view 显示的是当前路由地址所对应的内容-->
      </div>
    </template>
    <script>
    export default {
      name: 'App'
    }
    </script>
    

    然后修改入口js文件:main.js,修改之后如下

    import Vue from 'vue'
    import ElementUI from 'element-ui' //ui
    import 'element-ui/lib/theme-chalk/index.css'
    import locale from 'element-ui/lib/locale/lang/zh-CN' // lang i18n
    import '@/styles/index.scss' // global css 核心css
    
    import App from './App'
    //import '@/icons' // icon
    import router from './router/index' //router
    
    // Vue.config.productionTip = false
    Vue.use(ElementUI);
    
    /* eslint-disable no-new */
    new Vue({
      el: '#app',
      router,
      components: { App },
      template: '<App/>'
    })
    

    从上面可以看出引用了element-ui,所以事先要导入,导入有两种方式:
    第一种,npm命令导入,可以指定版本号,在@后面加版本号

    npm i element-ui@版本号
    

    第二种,直接到项目的package.json配置文件的“dependencies”属性下添加

    "dependencies": {
        "element-ui": "^2.13.0",
      },
    

    然后npm命令手动导入,会将新加的js包下载到本项目,类似maven的pom.xml文件中添加新的jar依赖,再用maven重新导入。此处用的是最新的版本,版本号加在^后面。

    npm i
    

    到官网查看element-ui的历史版本号


    ele.png

    2.添加核心css文件,新建styles文件夹,先展示需要添加的css文件目录结构


    css.png

    index.scss

    @import './variables.scss';
    @import './mixin.scss';
    @import './transition.scss';
    @import './element-ui.scss';
    @import './sidebar.scss';
    
    body {
      margin: 0; //原始页面会多出8px边距
      height: 100%;
      -moz-osx-font-smoothing: grayscale;
      -webkit-font-smoothing: antialiased;
      text-rendering: optimizeLegibility;
      font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Arial, sans-serif;
    }
    
    .el-dialog__body {
      padding: 20px 20px;
      color: #606266;
      font-size: 14px;
    }
    
    label {
      font-weight: 700;
    }
    
    html {
      height: 100%;
      box-sizing: border-box;
    }
    
    #app{
      height: 100%;
    }
    
    *,
    *:before,
    *:after {
      box-sizing: inherit;
    }
    
    a,
    a:focus,
    a:hover {
      cursor: pointer;
      color: inherit;
      outline: none;
      text-decoration: none;
    }
    
    div:focus{
      outline: none;
    }
    
    a:focus,
    a:active {
      outline: none;
    }
    
    a,
    a:focus,
    a:hover {
      cursor: pointer;
      color: inherit;
      text-decoration: none;
    }
    
    .clearfix {
      &:after {
        visibility: hidden;
        display: block;
        font-size: 0;
        content: " ";
        clear: both;
        height: 0;
      }
    }
    
    .warn-content{
      background: rgba(66,185,131,.1);
      border-radius: 2px;
      padding: 11px;
      word-spacing: .05rem;
      a{
        color: #42b983;
        font-weight: 400;
      }
    }
    
    //main-container全局样式
    .app-main{
      height: 100%;
    }
    
    .app-container {
      padding: 20px;
    }
    
    .head-container {
      padding-bottom: 10px;
      .filter-item {
        display: inline-block;
        vertical-align: middle;
        margin-bottom: 10px;
      }
      input {
        height: 30.5px;
        line-height: 30.5px;
      }
      .el-input__icon {
        line-height: 31px;
      }
    }
    
    .el-avatar {
      display: inline-block;
      text-align: center;
      background: #ccc;
      color: #fff;
      white-space: nowrap;
      position: relative;
      overflow: hidden;
      vertical-align: middle;
      width: 32px;
      height: 32px;
      line-height: 32px;
      border-radius: 16px;
    }
    
    .logo-con{
      height: 60px;
      padding: 13px 0px 0px;
      img{
        height: 32px;
        width: 135px;
        display: block;
        //margin: 0 auto;
      }
    }
    
    #el-login-footer {
      height: 40px;
      line-height: 40px;
      position: fixed;
      bottom: 0;
      width: 100%;
      text-align: center;
      color: #fff;
      font-family: Arial;
      font-size: 12px;
      letter-spacing: 1px;
    }
    
    #el-main-footer {
      background: none repeat scroll 0 0 white;
      border-top: 1px solid #e7eaec;
      overflow: hidden;
      padding: 10px 6px 0px 6px;
      height: 33px;
      font-size: 0.7rem !important;
      color: #7a8b9a;
      letter-spacing: 0.8px;
      font-family: Arial, sans-serif !important;
      position: fixed;
      bottom: 0;
      z-index: 99;
      width: 100%;
    }
    .eladmin-upload {
      border: 1px dashed #c0ccda;
      border-radius: 5px;
      height: 45px;
      line-height: 45px;
      width: 368px;
    }
    

    variables.scss

    #app {
      // 主体区域
      .main-container {
        min-height: 100%;
        transition: margin-left .28s;
        margin-left: 193px;
        position: relative;
      }
      // 侧边栏
      .sidebar-container {
        transition: width 0.28s;
        width: 193px !important;
        height: 100%;
        position: fixed;
        font-size: 0px;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 1001;
        overflow: hidden;
        //reset element-ui css
        .horizontal-collapse-transition {
          transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
        }
        .el-scrollbar__bar.is-vertical{
          right: 0px;
        }
        .scrollbar-wrapper {
          overflow-x: hidden!important;
          .el-scrollbar__view {
            height: 100%;
          }
        }
        .is-horizontal {
          display: none;
        }
        a {
          display: inline-block;
          width: 100%;
          overflow: hidden;
        }
        .svg-icon {
          margin-right: 16px;
        }
        .el-menu {
          border: none;
          height: 100%;
          width: 100% !important;
        }
        .is-active > .el-submenu__title{
          color: #f4f4f5!important;
        }
      }
      .hideSidebar {
        .sidebar-container {
          width: 36px !important;
        }
        .main-container {
          margin-left: 36px;
        }
        .submenu-title-noDropdown {
          padding-left: 10px !important;
          position: relative;
          .el-tooltip {
            padding: 0 10px !important;
          }
        }
        .el-submenu {
          overflow: hidden;
          &>.el-submenu__title {
            padding-left: 10px !important;
            .el-submenu__icon-arrow {
              display: none;
            }
          }
        }
        .el-menu--collapse {
          .el-submenu {
            &>.el-submenu__title {
              &>span {
                height: 0;
                width: 0;
                overflow: hidden;
                visibility: hidden;
                display: inline-block;
              }
            }
          }
        }
      }
      .sidebar-container .nest-menu .el-submenu>.el-submenu__title,
      .sidebar-container .el-submenu .el-menu-item {
        min-width: 195px !important;
        background-color: $subMenuBg !important;
        &:hover {
          background-color: $menuHover !important;
        }
      }
      .el-menu--collapse .el-menu .el-submenu {
        min-width: 195px !important;
      }
    
      //适配移动端
      .mobile {
        .main-container {
          margin-left: 0px;
        }
        .sidebar-container {
          transition: transform .28s;
          width: 195px !important;
        }
        &.hideSidebar {
          .sidebar-container {
            transition-duration: 0.3s;
            transform: translate3d(-195px, 0, 0);
          }
        }
      }
      .withoutAnimation {
        .main-container,
        .sidebar-container {
          transition: none;
        }
      }
    }
    
    .el-menu--vertical{
      & >.el-menu{
        .svg-icon{
          margin-right: 16px;
        }
      }
    }
    

    mixin.scss

    @mixin clearfix {
      &:after {
        content: "";
        display: table;
        clear: both;
      }
    }
    
    @mixin scrollBar {
      &::-webkit-scrollbar-track-piece {
        background: #d3dce6;
      }
      &::-webkit-scrollbar {
        width: 6px;
      }
      &::-webkit-scrollbar-thumb {
        background: #99a9bf;
        border-radius: 20px;
      }
    }
    
    @mixin relative {
      position: relative;
      width: 100%;
      height: 100%;
    }
    

    transition.scss

    //globl transition css
    
    /*fade*/
    .fade-enter-active,
    .fade-leave-active {
      transition: opacity 0.28s;
    }
    
    .fade-enter,
    .fade-leave-active {
      opacity: 0;
    }
    
    /*fade-transform*/
    .fade-transform-leave-active,
    .fade-transform-enter-active {
      transition: all .5s;
    }
    .fade-transform-enter {
      opacity: 0;
      transform: translateX(-30px);
    }
    .fade-transform-leave-to {
      opacity: 0;
      transform: translateX(30px);
    }
    
    /*fade*/
    .breadcrumb-enter-active,
    .breadcrumb-leave-active {
      transition: all .5s;
    }
    
    .breadcrumb-enter,
    .breadcrumb-leave-active {
      opacity: 0;
      transform: translateX(20px);
    }
    
    .breadcrumb-move {
      transition: all .5s;
    }
    
    .breadcrumb-leave-active {
      position: absolute;
    }
    

    element-ui.scss

     //to reset element-ui default css
    .el-upload {
      input[type="file"] {
        display: none !important;
      }
    }
    
    .el-upload__input {
      display: none;
    }
    
    //暂时性解决diolag 问题 https://github.com/ElemeFE/element/issues/2461
    .el-dialog {
      transform: none;
      left: 0;
      position: relative;
      margin: 0 auto;
    }
    
    //element ui upload
    .upload-container {
      .el-upload {
        width: 100%;
        .el-upload-dragger {
          width: 100%;
          height: 200px;
        }
      }
    }
    

    sidebar.scss

    #app {
      // 主体区域
      .main-container {
        min-height: 100%;
        transition: margin-left .28s;
        margin-left: 193px;
        position: relative;
      }
      // 侧边栏
      .sidebar-container {
        transition: width 0.28s;
        width: 193px !important;
        height: 100%;
        position: fixed;
        font-size: 0px;
        top: 0;
        bottom: 0;
        left: 0;
        z-index: 1001;
        overflow: hidden;
        //reset element-ui css
        .horizontal-collapse-transition {
          transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out;
        }
        .el-scrollbar__bar.is-vertical{
          right: 0px;
        }
        .scrollbar-wrapper {
          overflow-x: hidden!important;
          .el-scrollbar__view {
            height: 100%;
          }
        }
        .is-horizontal {
          display: none;
        }
        a {
          display: inline-block;
          width: 100%;
          overflow: hidden;
        }
        .svg-icon {
          margin-right: 16px;
        }
        .el-menu {
          border: none;
          height: 100%;
          width: 100% !important;
        }
        .is-active > .el-submenu__title{
          color: #f4f4f5!important;
        }
      }
      .hideSidebar {
        .sidebar-container {
          width: 36px !important;
        }
        .main-container {
          margin-left: 36px;
        }
        .submenu-title-noDropdown {
          padding-left: 10px !important;
          position: relative;
          .el-tooltip {
            padding: 0 10px !important;
          }
        }
        .el-submenu {
          overflow: hidden;
          &>.el-submenu__title {
            padding-left: 10px !important;
            .el-submenu__icon-arrow {
              display: none;
            }
          }
        }
        .el-menu--collapse {
          .el-submenu {
            &>.el-submenu__title {
              &>span {
                height: 0;
                width: 0;
                overflow: hidden;
                visibility: hidden;
                display: inline-block;
              }
            }
          }
        }
      }
      .sidebar-container .nest-menu .el-submenu>.el-submenu__title,
      .sidebar-container .el-submenu .el-menu-item {
        min-width: 195px !important;
        background-color: $subMenuBg !important;
        &:hover {
          background-color: $menuHover !important;
        }
      }
      .el-menu--collapse .el-menu .el-submenu {
        min-width: 195px !important;
      }
    
      //适配移动端
      .mobile {
        .main-container {
          margin-left: 0px;
        }
        .sidebar-container {
          transition: transform .28s;
          width: 195px !important;
        }
        &.hideSidebar {
          .sidebar-container {
            transition-duration: 0.3s;
            transform: translate3d(-195px, 0, 0);
          }
        }
      }
      .withoutAnimation {
        .main-container,
        .sidebar-container {
          transition: none;
        }
      }
    }
    
    .el-menu--vertical{
      & >.el-menu{
        .svg-icon{
          margin-right: 16px;
        }
      }
    }
    

    3.修改route,加载登录页面

    import Vue from 'vue'
    import Router from 'vue-router'
    import login from '@/views/login' //导入login组件
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: '/',
          name: 'login', 
          component: login //组件
        }
      ]
    })
    

    4.编写login,新建views文件夹,添加login.vue,内容如下

    <template>
      <div class="login">
        <el-form ref="loginForm" :model="loginForm" :rules="loginRules" label-position="left" label-width="0px" class="login-form">
          <h3 class="title">EL-ADMIN 后台管理系统</h3>
          <el-form-item prop="username">
            <el-input v-model="loginForm.username" type="text" auto-complete="off" placeholder="账号">
              <svg-icon slot="prefix" icon-class="user" class="el-input__icon input-icon"/>
            </el-input>
          </el-form-item>
          <el-form-item prop="password">
            <el-input v-model="loginForm.password" type="password" auto-complete="off" placeholder="密码" @keyup.enter.native="handleLogin">
              <svg-icon slot="prefix" icon-class="password" class="el-input__icon input-icon"/>
            </el-input>
          </el-form-item>
          <el-form-item prop="code">
            <el-input v-model="loginForm.code" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter.native="handleLogin">
              <svg-icon slot="prefix" icon-class="validCode" class="el-input__icon input-icon"/>
            </el-input>
            <div class="login-code">
              <img :src="codeUrl" @click="getCode">
            </div>
          </el-form-item>
          <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住我</el-checkbox>
          <el-form-item style="width:100%;">
            <el-button :loading="loading" size="medium" type="primary" style="width:100%;" @click.native.prevent="handleLogin">
              <span v-if="!loading">登 录</span>
              <span v-else>登 录 中...</span>
            </el-button>
          </el-form-item>
        </el-form>
        <!--  底部  -->
        <div id="el-login-footer">
          <span>© 2020 Zhou Yi</span>
          <span> ⋅ </span>
          <a href="http://www.beian.miit.gov.cn" target="_blank">渝ICP备18005431号</a>
        </div>
      </div>
    </template>
    
    <script>
    import { encrypt } from '@/utils/rsaEncrypt' //加密
    import Config from '@/config' //全局配置
    import { getCodeImg } from '@/api/login' //api
    import Cookies from 'js-cookie' //cookie
    export default {
      name: 'Login',
      data() {
        return {
          codeUrl: '',
          cookiePass: '',
          loginForm: {
            username: 'admin',
            password: '123456',
            rememberMe: false,
            code: '',
            uuid: ''
          },
          loginRules: {
            username: [{ required: true, trigger: 'blur', message: '用户名不能为空' }],
            password: [{ required: true, trigger: 'blur', message: '密码不能为空' }],
            code: [{ required: true, trigger: 'change', message: '验证码不能为空' }]
          },
          loading: false,
          redirect: undefined
        }
      },
      watch: {
        $route: {
          handler: function(route) {
            this.redirect = route.query && route.query.redirect
          },
          immediate: true
        }
      },
      created() {
        // this.getCode()
        // this.getCookie()
      },
      methods: {
        getCode() {
          getCodeImg().then(res => {
            this.codeUrl = res.img
            this.loginForm.uuid = res.uuid
          })
        },
        getCookie() {
          const username = Cookies.get('username')
          let password = Cookies.get('password')
          const rememberMe = Cookies.get('rememberMe')
          // 保存cookie里面的加密后的密码
          this.cookiePass = password === undefined ? '' : password
          password = password === undefined ? this.loginForm.password : password
          this.loginForm = {
            username: username === undefined ? this.loginForm.username : username,
            password: password,
            rememberMe: rememberMe === undefined ? false : Boolean(rememberMe),
            code: ''
          }
        },
        handleLogin() {
          this.$refs.loginForm.validate(valid => {
            const user = {
              username: this.loginForm.username,
              password: this.loginForm.password,
              rememberMe: this.loginForm.rememberMe,
              code: this.loginForm.code,
              uuid: this.loginForm.uuid
            }
            if (user.password !== this.cookiePass) {
              user.password = encrypt(user.password)
            }
            if (valid) {
              this.loading = true
              if (user.rememberMe) {
                Cookies.set('username', user.username, { expires: Config.passCookieExpires })
                Cookies.set('password', user.password, { expires: Config.passCookieExpires })
                Cookies.set('rememberMe', user.rememberMe, { expires: Config.passCookieExpires })
              } else {
                Cookies.remove('username')
                Cookies.remove('password')
                Cookies.remove('rememberMe')
              }
              this.$store.dispatch('Login', user).then(() => {
                this.loading = false
                this.$router.push({ path: this.redirect || '/' })
              }).catch(() => {
                this.loading = false
                this.getCode()
              })
            } else {
              console.log('error submit!!')
              return false
            }
          })
        }
      }
    }
    </script>
    //scss语法
    <style rel="stylesheet/scss" lang="scss">
      .login {
        display: flex; //flex布局
        justify-content: center; //水平居中
        align-items: center; //上下居中
        height: 100%;
        background-image:url(http://api.neweb.top/bing.php?type=rand );
        background-size: cover;
      }
      .title {
        margin: 0px auto 30px auto;
        text-align: center;
        color: #707070;
      }
    
      .login-form {
        border-radius: 6px;
        background: #ffffff;
        width: 385px;
        padding: 25px 25px 5px 25px;
        .el-input {
          height: 38px;
          input {
            height: 38px;
          }
        }
        .input-icon{
          height: 39px;width: 14px;margin-left: 2px;
        }
      }
      .login-tip {
        font-size: 13px;
        text-align: center;
        color: #bfbfbf;
      }
      .login-code {
        width: 33%;
        display: inline-block;
        height: 38px;
        float: right;
        img{
          cursor: pointer;
          vertical-align:middle
        }
      }
      #el-login-footer {
        position: fixed;
        bottom: 0;
        width: 100%;
        height: 40px;
        line-height: 40px;
        text-align: center;
        color: #fff;
        font-family: Arial,serif;
        font-size: 12px;
        letter-spacing: 1px;
      }
      a{
        cursor: pointer;
        color: inherit;
        text-decoration: none;
      }
    </style>
    

    首先样式使用scss语法,布局采用flex布局
    又引入了新的包,先解决js-cookie,注意:采用scss语法需要导入node-sass和sass-loader,直接在package.json中添加,npm i 命令导入

    "dependencies": {
        "js-cookie": "2.2.0",
      },
     "devDependencies": {
        "node-sass": "^4.7.2",
        "sass-loader": "7.0.3",
     },
    

    接下来新建utils文件夹,添加rsaEncrypt.js

    import JSEncrypt from 'jsencrypt/bin/jsencrypt'
    
    // 密钥对生成 http://web.chacuo.net/netrsakeypair
    
    const publicKey = 'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD\n' +
      '2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ=='
    
    const privateKey = 'MIIBUwIBADANBgkqhkiG9w0BAQEFAASCAT0wggE5AgEAAkEA0vfvyTdGJkdbHkB8\n' +
      'mp0f3FE0GYP3AYPaJF7jUd1M0XxFSE2ceK3k2kw20YvQ09NJKk+OMjWQl9WitG9p\n' +
      'B6tSCQIDAQABAkA2SimBrWC2/wvauBuYqjCFwLvYiRYqZKThUS3MZlebXJiLB+Ue\n' +
      '/gUifAAKIg1avttUZsHBHrop4qfJCwAI0+YRAiEA+W3NK/RaXtnRqmoUUkb59zsZ\n' +
      'UBLpvZgQPfj1MhyHDz0CIQDYhsAhPJ3mgS64NbUZmGWuuNKp5coY2GIj/zYDMJp6\n' +
      'vQIgUueLFXv/eZ1ekgz2Oi67MNCk5jeTF2BurZqNLR3MSmUCIFT3Q6uHMtsB9Eha\n' +
      '4u7hS31tj1UWE+D+ADzp59MGnoftAiBeHT7gDMuqeJHPL4b+kC+gzV4FGTfhR9q3\n' +
      'tTbklZkD2A=='
    
    // 加密
    export function encrypt(txt) {
      const encryptor = new JSEncrypt()
      encryptor.setPublicKey(publicKey) // 设置公钥
      return encryptor.encrypt(txt) // 对需要加密的数据进行加密
    }
    
    // 解密
    export function decrypt(txt) {
      const encryptor = new JSEncrypt()
      encryptor.setPrivateKey(privateKey)
      return encryptor.decrypt(txt)
    }
    

    再新建config文件夹,添加index.js全局配置文件

    /**
     * @description 系统全局配置
     */
    export default {
        /**
         * @description 记住密码状态下的token在Cookie中存储的天数,默认1天
         */
        tokenCookieExpires: 1,
        /**
         * @description 记住密码状态下的密码在Cookie中存储的天数,默认1天
         */
        passCookieExpires: 1,
        /**
         * @description 此处修改网站名称
         */
        webName: 'EL-ADMIN',
        /**
         * @description 是否只保持一个子菜单的展开
         */
        uniqueOpened: true,
        /**
         * @description token key
         */
        TokenKey: 'EL-ADMIN-TOEKN',
      
        /**
         * @description 请求超时时间,毫秒(默认2分钟)
         */
        timeout: 1200000,
      
        /**
         * @description 是否显示 tagsView
         */
        tagsView: true,
      
        /**
         * @description 固定头部
         */
        fixedHeader: true,
      
        /**
         * @description 是否显示logo
         */
        sidebarLogo: true,
      
        /**
         * 是否显示设置的悬浮按钮
         */
        settingBtn: false,
      }
    

    新建api文件夹,添加login.js

    import request from '@/utils/request'
    
    export function login(username, password, code, uuid) {
      return request({
        url: 'auth/login',
        method: 'post',
        data: {
          username,
          password,
          code,
          uuid
        }
      })
    }
    
    export function getInfo() {
      return request({
        url: 'auth/info',
        method: 'get'
      })
    }
    
    export function getCodeImg() {
      return request({
        url: 'auth/captcha',
        method: 'get'
      })
    }
    
    export function logout() {
      return request({
        url: 'auth/logout',
        method: 'delete'
      })
    }
    

    在utils文件夹下添加request.js

    import axios from 'axios'
    import Config from '@/config'
    
    // 创建axios实例
    const service = axios.create({
      baseURL: process.env.NODE_ENV === 'production' ? process.env.BASE_API : '/', // api 的 base_url
      timeout: Config.timeout // 请求超时时间
    })
    export default service
    

    此处需要引入axios

      "dependencies": {
        "axios": "^0.19.0",
      },
    

    5.启动项目测试

    npm run dev
    

    启动成功


    start.png

    浏览器输入:http://localhost:8080

    login.png

    后面还有svg-icon、store、以及与后台交互测试。

    模仿EL-ADMIN 后台管理系统

    相关文章

      网友评论

          本文标题:实现Vue+Element-ui登录

          本文链接:https://www.haomeiwen.com/subject/juyguhtx.html