效果演示

  1. 昨天的项目 基础上对前端【登录界面】进行美化
    点击小眼睛实现密码可见:(原理很简单,见后面源码解析)
    在这里插入图片描述在这里插入图片描述
    登录监听:

    1. 加载动画
      在这里插入图片描述在这里插入图片描述
    2. 错误提示
      在这里插入图片描述在这里插入图片描述
      3.成功提示
      在这里插入图片描述
  2. 改写router实现左侧树型导航栏

    展开前:
    在这里插入图片描述
    展开后:
    在这里插入图片描述

本章源码

Cungudafa——Github:Vue_SpringbootDay2
快速链接:https://github.com/cungudafa/Vue/

正文

一、美化Vue登录界面

1.1 引入SvgIcon

此部分参考:vue中引入.svg图标,使用iconfont图标库

  1. 用node引入SvgIcon

    cnpm i svg-sprite-loader --save

  2. 新建src/icons/ 文件夹,

    1. 创建svg文件夹(用于存放扩展的.svg图标,svg下载可以在我github此项目中下载)
    2. 创建index.js(这是icons的全局声明,待会儿会注入main.js)
      在这里插入图片描述

    icons/index.js

    import Vue from 'vue'
    import SvgIcon from '@/components/SvgIcon'// svg组件
    
    // register globally
    Vue.component('svg-icon', SvgIcon)
    
    const requireAll = requireContext => requireContext.keys().map(requireContext)
    const req = require.context('./svg', false, /\.svg$/)
    requireAll(req)
    
    
  3. main.js中引入图标

    import './icons'// 引入图标
    
  4. src/compoments 目录下新建SvgIcon目录,并新建index.vue
    这是样式布局,在之前的icons/index.js 已经引入过了,目的是为了描述icons这个组件样式。
    在这里插入图片描述
    src/compoments/SvgIcon/index.vue

    <template>
      <svg :class="svgClass" aria-hidden="true">
        <use :xlink:href="iconName"></use>
      </svg>
    </template>
    
    <script>
    export default {
      name: 'svg-icon',
      props: {
        iconClass: {
          type: String,
          required: true
        },
        className: {
          type: String
        }
      },
      computed: {
        iconName() {
          return `#icon-${this.iconClass}`
        },
        svgClass() {
          if (this.className) {
            return 'svg-icon ' + this.className
          } else {
            return 'svg-icon'
          }
        }
      }
    }
    </script>
    
    <style scoped>
    .svg-icon {
      width: 1.2em;
      height: 1.2em;
      vertical-align: -0.18em;
      fill: currentColor;
      overflow: hidden;
    }
    </style>
    
    
  5. 配置(这是和之前都没有做过的一步,需仔细)

    1. build/webpack.base.conf.js文件中,找到module: { rules: [加入…下面…]
       	 {//新加===
              test: /\.svg$/,
              loader: 'svg-sprite-loader',
              include: [resolve('src/icons')],
              options: {
                symbolId: 'icon-[name]'
              }
            }
      
    2. 并在以下设置中添加exclude: [resolve('src/icons')],如下所示
            {
              test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
              loader: 'url-loader',
              exclude: [resolve('src/icons')],//新加====
              options: {
                limit: 10000,
                name: utils.assetsPath('img/[name].[hash:7].[ext]')
              }
            },
      
  6. 使用方法

    <svg-icon icon-class="user" />
    

    user就是这个图标效果:
    在这里插入图片描述

1.2 优化Login.vue

代码可读性和之前没做什么大的修改,主要是多加了el-card标签和SvgIcon;不过多描述。
Login.vue

<template>
  <div class="logindemo">
    <el-card class="login-form-layout">
      <el-form autocomplete="on" :model="user" :rules="rules" ref="loginForm" status-icon label-position="left">
        <div style="text-align: center">
          <svg-icon icon-class="login-mall" style="width: 56px;height: 56px;color: #409EFF"></svg-icon>
        </div>
        <h2 class="login-title color-main">Vue-admin-web</h2>
        <el-form-item prop="name">
          <el-input name="name" type="text" v-model="user.name" autocomplete="on" placeholder="请输入用户名">
            <span slot="prefix">
              <svg-icon icon-class="user" class="color-main"></svg-icon>
            </span>
          </el-input>
        </el-form-item>
        <el-form-item prop="password">
          <el-input name="password" :type="pwdType" v-model="user.password" @keyup.enter.native="submit"
            autocomplete="on" placeholder="请输入密码">
            <span slot="prefix">
              <svg-icon icon-class="password" class="color-main"></svg-icon>
            </span>
            <span slot="suffix" @click="showPwd">
              <svg-icon icon-class="eye" class="color-main"></svg-icon>
            </span>
          </el-input>
        </el-form-item>
        <el-form-item style="margin-bottom: 60px">
          <el-button style="width: 100%" type="primary" :loading="loading" @click.native.prevent="submit">登录</el-button>
        </el-form-item>
      </el-form>
    </el-card>
  </div>
</template>

<script>
  import {Loading}from 'element-ui'
  export default {
    name: "logindemo",
    data() {
      return {
        loading: false,
        pwdType: "password",
        user: {},
        rules: {
          name: [{
            required: true,
            message: '用户名不能为空',
            trigger: 'blur'
          }],
          password: [{
            required: true,
            message: '密码不能为空',
            trigger: 'blur'
          }],
        },
      };
    },
    methods: {
      //登录button上加载提示:小圆圈控制
      startLoading: function() {
        loading = Loading.service({
            lock: true,
            background: 'rgba(0, 0, 0, 0.7)'
        })
       },
      endLoading: function() {
            loading.close()
       },
      //小眼睛选择密码是否可见
      showPwd() {
        if (this.pwdType === "password") {
          this.pwdType = "";
        } else {
          this.pwdType = "password";
        }
      },
      //提交表单,登录验证
      submit() {
        this.$refs.loginForm.validate(valid => {//对应el-form的ref属性
          this.loading = true;//开始登录button上小圆圈转
          if (valid) {//表单正确格式填写
            this.$ajax.post('/user/check',this.user).then((res) => {//axios发送post请求,后台验证
            if (res.data) {
              this.$store.dispatch("login", res.data).then(() => {//验证结果res.data存储在vuex.vue全局数据控制函数login中
                  this.loading = false;//登录成功,小圆圈停止
                  this.$notify({
                    type: 'success',
                    message: '欢迎你,' + res.data.name + '!',
                    duration: 3000
                  })
                  //this.$router.replace('/')
                  this.$router.push({ name: 'layoutYHGL'})//结合router/index.js规则,根据name跳转到指定url
                })
                } else {
                  this.loading = false;//登录错误,小圆圈不转
                  this.$message({
                    type: 'error',
                    message: '用户名或密码错误',
                    showClose: true
                  })
                }
              }).catch(() => {
                this.loading = false;//登录错误,小圆圈不转
                this.$message({
                  type: 'error',
                  message: '网络错误,请重试',
                  showClose: true
                })
              });
          } else {
            // eslint-disable-next-line no-console
            console.log("参数验证不合法!");
            return false;
          }
        });
      }
    }
  };
</script>

<style scoped>
  .login-form-layout {
    position: absolute;
    left: 0;
    right: 0;
    width: 360px;
    margin: 140px auto;
    border-top: 10px solid #409eff;
  }

  .login-title {
    text-align: center;
  }

  .login-center-layout {
    background: #409eff;
    width: auto;
    height: auto;
    max-width: 100%;
    max-height: 100%;
    margin-top: 200px;
  }
</style>

1.3 最终效果 ?

1、card:最外层边框,附带阴影效果
2、svgicon:效果就是界面里的头像,小锁,眼睛
在这里插入图片描述
3、点击小眼睛实现密码可见:(原理很简单,见上面源码)
在这里插入图片描述在这里插入图片描述

二、 左侧路由实现

vue-router在前面都导入过,这里跳过一步配置router,不了解可以参考 前面一文

2.1 优化目录结构

为了区分components 目录的作用,这里把Login.vue放在views 目录下并作区分:
并如图创建layoutloginuser目录及vue;这样代码可读性更好。
在这里插入图片描述

2.2 router与layout.vue

  1. router/index.js中主要是

    1. path:声明url动向
    2. name:命名当前path名称,在其他vue页面 js中使用this.$router.push({ name: 'path'}),浏览器返回path页面
    3. component:指明当前url下调用vue模块的位置
    4. meta:导航信息封装在meta,在layout.vue页面,循环调用item.meta.funcNode打印出菜单栏
    5. children:二级菜单,funcNode1-1、1-2等来区分排序;在layout.vue页面,循环调用itemC.meta.funcNode打印出菜单栏,注意是itemC

    一个模块举例说明:

     	  {
    	      path: '/user',
    	      name: 'layoutYHGL',//login.vue下js中由this.$router.push({ name: 'layoutYHGL'})使浏览器返回/user页面
    	      component: () => import('@/views/layout/Layout'),//没有在前面import,直接写引用处,可读性强
    	      meta: {//导航信息封装在meta
    	        title: '用户管理',//菜单栏界面提示内容
    	        icon: 'el-icon-user',//图标
    	        menu: true,
    	        funcNode: '1'//layout.vue读取这个数实现动态定位
    	      },
    	      children: [
    	      	  {//二级目录
    	      		funcNode: '1-1'
    	          },
    	          {
    	      		funcNode: '1-2'
    	          }
    	      ]
    	   }
    

    完整的:router/index.js

    import Vue from 'vue'
    import Router from 'vue-router'
    import ElementUI from 'element-ui' // 导航菜单栏样式
    import 'element-ui/lib/theme-chalk/index.css'
    
    Vue.use(Router) //注册vue-router
    Vue.use(ElementUI);
    
    export default new Router({
      mode: 'history',//跳转时不带#号
      routes: [{
          path: '/',
          redirect: '/login',
          meta: {
            menu: false
          }
        },
        {
          path: '/login',
          name: 'Login',
          component: () => import('@/views/login/Login'),
          meta: {
            menu: false
          }
        },
        {
          path: '/user',
          name: 'layoutYHGL',//login.vue下js中由this.$router.push({ name: 'layoutYHGL'})使浏览器返回/user页面
          component: () => import('@/views/layout/Layout'),//没有在前面import,直接写引用处,可读性强
          meta: {//导航信息封装在meta
            title: '用户管理',//菜单栏界面提示内容
            icon: 'el-icon-user',//图标
            menu: true,
            funcNode: '1'//layout.vue读取这个数实现动态定位
          },
          children: [{//二级目录
              path: '/user/userList',
              name: 'UserList',
              component: () => import('@/views/user/UserList'),
              meta: {
                title: '用户列表',
                icon: 'el-icon-notebook-2',
                menu: true,
                funcNode: '1-1'
              }
            },
            {
              path: '/user/addUser',
              name: 'UserAdd',
              component: () => import('@/views/user/UserList'),
              meta: {
                title: '用户添加',
                icon: 'el-icon-circle-plus-outline',
                menu: true,
                funcNode: '1-2'
              }
            }
          ]
        },
        {
          path: '/role',
          redirect: 'user/userList',
          meta: {
            title: '角色管理',
            icon: 'el-icon-help',
            menu: true
          }
        },
        {
          path: '/sys',
          name: 'layoutXTGL',
          component: () => import('@/views/layout/Layout'),
          meta: {
            title: '系统管理',
            icon: 'el-icon-setting',
            menu: true,
            funcNode: '2'
          },
          children: [{
            path: '/sys/sysLogList',
            name: 'SysLogList',
            component: () => import('@/views/user/UserList'),
            meta: {
              title: '系统访问日志',
              icon: 'el-icon-notebook-1',
              menu: true,
              funcNode: '2-1'
            }
          }]
        },
      ]
    })
    
    
  2. Layout.vue(和昨天的main.vue很相似,不过这里加了elements的菜单栏元素)
    解释见源码:

    <template>
      <el-container style="height: 100%;">
        <!-- 头部 -->
        <el-header style="text-align: right; font-size: 12px;">
          <el-dropdown>
            <i class="el-icon-setting" style="margin-right: 15px"></i>
            <el-dropdown-menu slot="dropdown">
              <el-dropdown-item>查看</el-dropdown-item>
              <el-dropdown-item>新增</el-dropdown-item>
              <el-dropdown-item>删除</el-dropdown-item>
            </el-dropdown-menu>
          </el-dropdown>
          <span v-if="user"> {{user.name}}
            <el-button type="warning" @click="logout">注销</el-button>
          </span>
        </el-header>
        <el-container>
          <!-- 左侧栏 -->
          <el-aside width="200px">
            <el-menu :default-active="onRoutes">
              <!--如果菜单(menu)是true 循环侧栏路由列表  -->
              <template v-for="item in menuData" v-if="item.meta.menu">
                <!-- 这里必须设置index,相当唯一标识这个菜单标签,否则菜单列表点击后随意展开 -->
                <el-submenu :index="''+item.meta.funcNode" :key="item.meta.funcNode">
                  <template slot="title"><i :class="item.meta.icon"></i>{{item.meta.title}}</template>
                  <!-- 如果菜单有孩子菜单,则循环孩子菜单 -->
                  <template v-for="itemC in item.children" v-if="item.children">
                    <el-menu-item :index="''+itemC.meta.funcNode" @click="clickMenu(itemC)" :key="itemC.meta.funcNode"><i :class="itemC.meta.icon"></i>{{itemC.meta.title}}</el-menu-item>
                  </template>
                </el-submenu>
              </template>
            </el-menu>
          </el-aside>
          <!-- 内容渲染 -->
          <el-main style="background-color: white;">
            <router-view />
          </el-main>
        </el-container>
      </el-container>
    </template>
    
    <script>
      export default {
        methods: {
          clickMenu(item) {
            this.$router.push({
              path: item.path
            }) //跳转的路由对象
            //this.$router.push({name:item.name})    通过name跳转
          },
          logout() {
            this.$store.dispatch('logout').then(() => {
              this.$router.replace('/login')
            })
          }
        },
        computed: {
          menuData(){//缓存router信息(前面我们说到的 meta),router控制
            return this.$router.options.routes
          },
          user() {//缓存user信息,vuex控制
            return this.$store.state.user
          },
          onRoutes() {//点击时此处高亮
            let path = this.$route.path.replace('/', '');
            return path ? path : '/';
          }
        }
      }
    </script>
    
    <style>
      .el-header {
        background-color: #FF9999;
        line-height: 60px;
      }
    
      .el-aside,
      .el-menu,
      .el-submenu,
      .el-menu-item {
        background-color: #FFCCCC;
    
      }
    
      body {
        margin: 0px;
      }
    </style>
    
    

2.3 el-header一致

因为我们在layout.vue中加了el-header,需要删除App.vue主界面的header
App.vue:

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style>
#app{}
</style>

2.4 菜单栏测试

  1. 在以下内容中仅放入了一个测试内容
    user/userList.vue
    <template>
      <div>
        userList
      </div>
    </template>
    
    <script>
    </script>
    
    <style>
    </style>
    
    
  2. 运行效果
    在这里插入图片描述

三、遇见的问题及总结

问题:

  1. Error in v-on handler: "TypeError: handler.apply is not a function"
    原因: .vue单文件中data() 里面的属性和methods里面的方法重名了,
    解决: 方面重新命名了一下和data里面的属性值检查是否正确对应。

  2. <svg-icon icon-class Error in v-on handler: "TypeError: Cannot read property 'validate' of undefined" found in...
    原因:SvgIcon没有正确引入
    解决方法:几番摸索,网上的教程都不全,自己东拼西凑出来的,不容易呀 ?

  3. @click.native.preventd父子组件通讯不成功,点击button无响应
    原因:参考教程
    解决方法:根据Vue2.0官方文档关于父子组件通讯的原则,父组件通过prop传递数据给子组件,子组件触发事件给父组件。但父组件想在子组件上监听自己的click的话,需要加上native修饰符
    在这里插入图片描述
    因为我们要实现:button上loading的监听,达到登录加载时以下效果:
    在这里插入图片描述

大功告成! ?

Logo

前往低代码交流专区

更多推荐