【第一个Vue上手小项目Day2】Element-ui+SvgIcon优雅的登录界面+导航栏
目录序效果演示本章源码正文一、美化Vue登录界面1.1 引入SvgIcon1.2 优化Login.vue1.3 最终效果 :)二、 左侧路由实现2.1 优化目录结构2.2 router与layout.vue2.3 el-header一致2.4 菜单栏测试三、遇见的问题及总结序效果演示在 昨天的项目 基础上对前端【登录界面】进行美化点击小眼睛实现密码可见:(原理很简单,见后面源码解析)...
目录
序
效果演示
-
在 昨天的项目 基础上对前端【登录界面】进行美化
点击小眼睛实现密码可见:(原理很简单,见后面源码解析)
登录监听:- 加载动画
- 错误提示
3.成功提示
- 加载动画
-
改写router实现左侧树型导航栏
展开前:
展开后:
本章源码
Cungudafa——Github:Vue_SpringbootDay2
快速链接:https://github.com/cungudafa/Vue/
正文
一、美化Vue登录界面
1.1 引入SvgIcon
-
用node引入SvgIcon
cnpm i svg-sprite-loader --save
-
新建src/icons/ 文件夹,
- 创建
svg
文件夹(用于存放扩展的.svg图标,svg下载可以在我github此项目中下载) - 创建
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)
- 创建
-
在
main.js
中引入图标import './icons'// 引入图标
-
在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>
-
配置(这是和之前都没有做过的一步,需仔细)
- 在build/webpack.base.conf.js文件中,找到
module: { rules: [
加入…下面…]
{//新加=== test: /\.svg$/, loader: 'svg-sprite-loader', include: [resolve('src/icons')], options: { symbolId: 'icon-[name]' } }
- 并在以下设置中添加
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]') } },
- 在build/webpack.base.conf.js文件中,找到
-
使用方法
<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 目录下并作区分:
并如图创建layout
,login
,user
目录及vue;这样代码可读性更好。
2.2 router与layout.vue
-
router/index.js
中主要是- path:声明url动向
- name:命名当前path名称,在其他vue页面 js中使用
this.$router.push({ name: 'path'})
,浏览器返回path页面 - component:指明当前url下调用vue模块的位置
- meta:导航信息封装在meta,在
layout.vue
页面,循环调用item.meta.funcNode
打印出菜单栏 - 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' } }] }, ] })
-
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 菜单栏测试
- 在以下内容中仅放入了一个测试内容
user/userList.vue
<template> <div> userList </div> </template> <script> </script> <style> </style>
- 运行效果
三、遇见的问题及总结
问题:
-
Error in v-on handler: "TypeError: handler.apply is not a function"
原因: .vue单文件中data() 里面的属性和methods
里面的方法重名了,
解决: 方面重新命名了一下和data里面的属性值检查是否正确对应。 -
<svg-icon icon-class Error in v-on handler: "TypeError: Cannot read property 'validate' of undefined" found in...
原因:SvgIcon没有正确引入
解决方法:几番摸索,网上的教程都不全,自己东拼西凑出来的,不容易呀 ? -
@click.native.preventd父子组件通讯不成功,点击button无响应
原因:参考教程
解决方法:根据Vue2.0官方文档关于父子组件通讯的原则,父组件通过prop传递数据给子组件,子组件触发事件给父组件。但父组件想在子组件上监听自己的click的话,需要加上native修饰符。
因为我们要实现:button上loading的监听,达到登录加载时以下效果:
大功告成! ?
更多推荐
所有评论(0)