目录

  • 前言
  • 正文
    • 一、登录页面
      • 安装 vue-router
      • config 配置文件
      • 1、App.vue
      • 2、login 页面
        • 安装 less-loader
        • 2.1、login-form.vue 布局代码
        • 2.2、login.vue 布局代码
        • Eslint 报错 "Parsing error: x-invalid-end-tag" 解决方案
    • 二、登录逻辑
      • 1、`$emit`的使用:子组件 login-form.vue中`handleSubmit`方法
      • 2、login.vue 中 `handleSubmit`方法
        • `mapActions`的使用
        • 安装Vuex
        • Vuex核心概念
        • 2.1 store文件夹:user.js中`handleLogin`方法和`getUserInfo`方法
        • 2.2 api文件夹:user.js中`login`方法和`getUserInfo`方法
        • 2.3 libs文件夹:api.request.js、axios.js
        • 安装axios
        • 引入mock
    • 三、登录页面跳转
      • 1、main.vue
      • 2、home.vue
      • 3、router路由
        • 3.1 routes.js代码
        • 3.2 index.js代码
        • 3.3 libs文件夹下:util.js,tools.js
        • 安装Cookie
      • 报错TypeError: vm.$t is not a function
        • 安装`vue-i18n`
    • 四、以上出现的若干 JavaScript 语法和 Vue 的相关使用解析
      • 1、props相关
      • 2、vue组件间传值ref、prop和emit
      • 3、`computed`计算属性
      • 4、`@keydown.enter.native`监听键盘的回车事件
  • 总结

前言

上一篇介绍了如何在 IDEA 中创建 Vue项目和导入 IView,这篇将介绍登陆页面及登录逻辑的实现。

正文

一、登录页面

安装 vue-router

Terminal 中输入

cnpm install vue-router --save

src 文件夹下创建 router 文件夹,router 文件夹下新建 index.js 和 routers.js 文件。

在 index.js 中导入 vue-router

import routes from './routers'
import Vue from 'vue'
import Router from 'vue-router'Vue.use(Router)

main.js中添加:

import router from '@/router'new Vue({el: '#app',router,components: { App },template: '<App/>'
})

config 配置文件

在 src 文件夹下创建 config 文件夹,在 config 文件夹下创建 index.js 文件。
(src 文件夹 => config 文件夹 => index.js)

export default {/*** @description 配置显示在浏览器标签的title*/title: 'iView-admin_demo1'
}

在 main.js 文件中导入 config

import config from '@/config'/*** @description 全局注册应用配置*/
Vue.prototype.$config = config

1、App.vue

<style>中代码修改如下:

<style lang="less">
#app {/*font-family: 'Avenir', Helvetica, Arial, sans-serif;*//*-webkit-font-smoothing: antialiased;*//*-moz-osx-font-smoothing: grayscale;*//*text-align: center;*//*color: #2c3e50;*//*margin-top: 60px;*/.size
}
.size{width: 100%;height: 100%;
}
html,body{.size;overflow: hidden;margin: 0;padding: 0;
}
</style>

2、login 页面

src 文件夹 => view 文件夹 => login 文件夹 => login.vue,login.less。

login.less 为 login 页面的样式,代码如下

.login{width: 100%;height: 100%;background-image: url('../../assets/images/login-bg.jpg');background-size: cover;background-position: center;position: relative;&-con{position: absolute;right: 160px;top: 50%;transform: translateY(-60%);width: 300px;&-header{font-size: 16px;font-weight: 300;text-align: center;padding: 30px 0;}.form-con{padding: 10px 0 0;}.login-tip{font-size: 10px;text-align: center;color: #c3c3c3;}}
}

在 vue 项目中使用 less,需要引入 less-loader。

安装 less-loader

在 Terminal 中输入

npm install less less-loader -- save

在 build 文件夹下 webpack.base.conf.js 文件中 module exports中的modulesrules中添加

{test: /\.less$/,loader: "style-loader!css-loader!less-loader",
}

2.1、login-form.vue 布局代码

因为 login.vue 中要用到 login-form组件,所以 components文件夹 => login-form.vue,login-form 布局代码如下:

<template><Form ref="loginForm" :model="form" :rules="rules" @keydown.enter.native="handleSubmit"><FormItem prop="userName"><Input v-model="form.userName" placeholder="请输入用户名"><span slot="prepend"><Icon :size="16" type="ios-person"></Icon></span></Input></FormItem><FormItem prop="password"><Input type="password" v-model="form.password" placeholder="请输入密码"><span slot="prepend"><Icon :size="14" type="md-lock"></Icon></span></Input></FormItem><FormItem><Button @click="handleSubmit" type="primary" long>登录</Button></FormItem></Form>
</template><script>
export default {name: 'login-form',props: {userNameRules: {type: Array,default: () => {return [{ required: true, message: '账号不能为空', trigger: 'blur' }]}},passwordRules: {type: Array,default: () => {return [{ required: true, message: '密码不能为空', trigger: 'blur' }]}}},data () {return {form: {userName: 'super_admin',password: ''}}},computed: {rules () {return {userName: this.userNameRules,password: this.passwordRules}}},methods: {handleSubmit () {//}}
}
</script><style scoped></style>

2.2、login.vue 布局代码

<template><div class="login"><div class="login-con"><Card icon="log-in" title="欢迎登录" :bordered="false"><div class="form-con"><login-form @on-success-valid="handleSubmit"></login-form><p class="login-tip">输入任意用户名和密码即可</p></div></Card></div></div>
</template><script>
import LoginForm from '@/components/login-form'
export default {name: 'login',components: {LoginForm},methods: {handleSubmit ({ userName, password }) {//}}
}
</script><style lang="less">@import './login';
</style>

Eslint 报错 “Parsing error: x-invalid-end-tag” 解决方案

以上运行时 IDEA 会在 </Input> 处报错 Parsing error: x-invalid-end-tag,解决如下。

首先找到

根目录 => .eslintrc.js => rules

添加一行代码

'vue/no-parsing-error': [2, { "x-invalid-end-tag": false }]

重启dev

npm run dev

问题解决。

登录页面成功显示,如下

二、登录逻辑

登录逻辑由 login.vue 中的 handleSubmit 方法实现。

1、$emit的使用:子组件 login-form.vue中handleSubmit方法

子组件 login-form.vue中handleSubmit方法是调用父组件login.vue中handleSubmit方法。使用到的emit是专门用于子组件触发父组件的方法并传递参数。

子组件 login-form.vue中handleSubmit方法

    handleSubmit () {this.$refs.loginForm.validate((valid) => {if (valid) {this.$emit('on-success-valid', {userName: this.form.userName,password: this.form.password})}})}

父组件 login.vue中相关代码

<login-form @on-success-valid="handleSubmit"></login-form>

其中on-success-valid是子组件中引用的别名,handleSubmit是父组件中定义的方法名称。

2、login.vue 中 handleSubmit方法

代码如下

  methods: {...mapActions(['handleLogin','getUserInfo']),handleSubmit ({ userName, password }) {this.handleLogin({ userName, password }).then(res => {this.getUserInfo().then(res => {this.$router.push({name: this.$config.homeName})})})}}

mapActions的使用

首先以上代码中使用到了 vuex中的 mapActions。

mapActions 里面是 store 里面的集合,使用 ES6 中解构赋值的方法进行获取我们所需的方法。前面的是 ES6 中拓展运算符,对我们所需的方法从数组中拓展出来。

安装Vuex

Terminal 中输入

cnpm install vuex --save

src文件夹 => store文件夹 => module文件夹、index.js

module文件夹 => app.js、user.js

index.js代码如下

import Vue from 'vue'
import Vuex from 'vuex'import user from './module/user'
import app from './module/app'Vue.use(Vuex)export default new Vuex.Store({state: {//},mutations: {//},actions: {//},modules: {user,app}
})

main.js中添加:

import store from './store'new Vue({el: '#app',router,store,components: { App },template: '<App/>'
})

Vuex核心概念

每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同:
1、Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store中的状态发生变化,那么相应的组件也会相应地得到高效更新。
2、你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。

  • State:存储 store 中的状态
  • Getter:顾名思义,获取 store 中的状态
  • Mutation:更改 state
  • Action:提交 Mutation,而不是直接变更状态;可包含任意异步操作

2.1 store文件夹:user.js中handleLogin方法和getUserInfo方法

import {login,getUserInfo
} from '@/api/user'
import {setToken, getToken} from '@/libs/util'export default {state: {token: getToken(),avatorImgPath: '',userName: '',userId: '',access: '',hasGetInfo: false},mutations: {setToken (state, token) {state.token = tokensetToken(token)},setAvator (state, avatorPath) {state.avatorImgPath = avatorPath},setUserName (state, name) {state.userName = name},setUserId (state, id) {state.userId = id},setAccess (state, access) {state.access = access},setHasGetInfo (state, status) {state.hasGetInfo = status}},getters: {//},actions: {// 登录handleLogin ({ commit }, { userName, password }) {userName = userName.trim() //trim()方法用于去除字符串的头尾空格return new Promise((resolve, reject) => { // Promise是一个用来处理异步操作的对象login({userName,password}).then(res => {const data = res.datacommit('setToken', data.token)resolve()}).catch(err => {reject(err)})})},// 获取用户相关信息getUserInfo ({ state, commit }) {return new Promise(((resolve, reject) => {try {getUserInfo(state.token).then(res => {const data = res.datacommit('setAvator', data.avator)commit('setUserName', data.name)commit('setUserId', data.user_id)commit('setAccess', data.access)commit('setHasGetInfo', true)resolve(data)}).catch(err => {reject(err)})} catch (error) {reject(error)}}))}}
}

如以上代码所示,用到了 api 文件夹下 user.js 文件中的logingetUserInfo方法

2.2 api文件夹:user.js中login方法和getUserInfo方法

src文件夹 => api文件夹 => user.js,相关方法代码如下:

import axios from '@/libs/api.request'export const login = ({ userName, password }) => {const data = {userName,password}return axios.request({url: 'login',data,method: 'post'})
}export const getUserInfo = (token) => {return axios.request({url: 'get_info',params: {token},method: 'get'})
}

如以上代码所示,用到了 libs文件夹下的 api.request.js文件中的 axios

2.3 libs文件夹:api.request.js、axios.js

src文件夹 => libs文件夹 => api.request.js,代码如下:

在这里插入代码片

src文件夹 => libs文件夹 => axios.js,代码如下:

import axios from 'axios'
import store from '@/store'const addErrorLog = errorInfo => {const {statusText, status, request: {responseURL}} = errorInfolet info = {type: 'ajax',code: status,mes: statusText,url: responseURL}if (!responseURL.includes('save_error_logger')) {store.dispatch('addErrorLog', info)}
}class HttpRequest {constructor (baseUrl = baseURL) {this.baseUrl = baseUrlthis.queue = {}}getInsideConfig () {const config = {baseURL: this.baseUrl,headers: {//}}return config}destroy (url) {delete this.queue[url]if (!Object.keys(this.queue).length) {//}}interceptors (instance, url) {// 请求拦截instance.interceptors.request.use(config => {// 添加全局的loading...if (!Object.keys(this.queue).length) {//}this.queue[url] = truereturn config}, error => {return Promise.reject(error)})// 响应拦截instance.interceptors.response.use(res => {this.destroy(url)const { data, status } = resreturn { data, status}}, error => {this.destroy(url)let errorInfo = error.responseif (!errorInfo) {const { request: { statusText, status }, config } = JSON.parse((JSON.stringify(error)))errorInfo = {statusText,status,request: { responseURL: config.url }}}addErrorLog(errorInfo)return Promise.reject(error)})}request (options) {const instance = axios.create()options = Object.assign(this.getInsideConfig(), options)this.interceptors(instance, options.url)return instance(options)}
}
export default HttpRequest

安装axios

Terminal中输入

cnpm install axios --save

axios.js 中输入

import axios from 'axios'

引入mock

Terminal中输入

cnpm install mockjs --save

src文件夹 => mock文件夹 => index.js、login.js

index.js中输入代码:

import Mock from 'mockjs'
import { login, logout, getUserInfo } from '@/mock/login'// 配置Ajax请求延时,可用来测试网络延迟大时项目中一些效果
Mock.setup({timeout: 1000
})Mock.mock(/\/login/, login)
Mock.mock(/\/get_info/, getUserInfo)

login.js是模拟用户登录的数据,代码如下:

import { getParams } from '@/libs/util'const USER_MAP = {super_admin: {name: 'super_admin',user_id: '1',access: ['super_admin', 'admin'],token: 'super_admin',avator: 'https://file.iviewui.com/dist/a0e88e83800f138b94d2414621bd9704.png'},admin: {name: 'admin',user_id: '2',access: ['admin'],token: 'admin',avator: 'https://avatars0.githubusercontent.com/u/20942571?s=460&v=4'}
}export const login = req => {req = JSON.parse(req.body)return { token: USER_MAP[req.userName].token }
}export const getUserInfo = req => {const params = getParams(req.url)return USER_MAP[params.token]
}export const logout = req => {return null
}

libs文件夹 => util.js,存放各种工具方法,getParams代码如下:

/*** @param {String} url* @description 从URL中解析参数*/
export const getParams = url => {const keyValueArr = url.split('?')[1].split('&')let paramObj = {}keyValueArr.forEach(item => {const keyValue = item.split('=')paramObj[keyValue[0]] = keyValue[1]})return paramObj
}

main.js中添加:

// 实际打包时应该不引入mock
if (process.env.NODE_ENV !== 'production') require('@/mock')

三、登录页面跳转

1、main.vue

src文件夹 => components文件夹 => main文件夹 => main.vue,main.less

main.less代码:

.main{.logo-con{height: 64px;padding: 10px;img{height: 44px;width: auto;display: block;margin: 0 auto;}}.header-con{background: #fff;padding: 0 20px;width: 100%;}.main-layout-con{height: 100%;overflow: hidden;}.main-content-con{height: ~"calc(100% - 60px)";overflow: hidden;}.tag-nav-wrapper{padding: 0;height:40px;background:#F0F0F0;}.content-wrapper{padding: 18px;height: ~"calc(100% - 80px)";overflow: auto;}.left-sider{.ivu-layout-sider-children{overflow-y: scroll;margin-right: -18px;}}
}
.ivu-menu-item > i{margin-right: 12px !important;
}
.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {margin-right: 8px !important;
}
.collased-menu-dropdown{width: 100%;margin: 0;line-height: normal;padding: 7px 0 6px 16px;clear: both;font-size: 12px !important;white-space: nowrap;list-style: none;cursor: pointer;transition: background 0.2s ease-in-out;&:hover{background: rgba(100, 100, 100, 0.1);}& * {color: #515a6e;}.ivu-menu-item > i{margin-right: 12px !important;}.ivu-menu-submenu > .ivu-menu > .ivu-menu-item > i {margin-right: 8px !important;}
}.ivu-select-dropdown.ivu-dropdown-transfer{max-height: 400px;
}

main.vue布局代码:

<template><Layout style="height: 100%" class="main"><Sider hide-trigger collapsible :width="256" :collapsed-width="64" v-model="collapsed" class="left-sider" :style="{overflow: 'hidden'}">左侧栏</Sider><Layout><Header class="header-con">标题栏</Header><Content class="main-content-con"><Layout class="main-layout-con"><div class="tag-nav-wrapper">1</div><Content class="content-wrapper">2</Content></Layout></Content></Layout></Layout>
</template><script>
export default {name: 'main',data () {return {collapsed: false}}
}
</script><style scoped>@import "./main.less";
</style>

2、home.vue

src文件夹 => view文件夹 => single-page文件夹 => home文件夹 => home.vue

home.vue布局代码如下:

<template><div><Row :gutter="20">行1</Row><Row :gutter="20" style="margin-top: 10px;">行2</Row><Row>行3</Row></div>
</template><script>
export default {name: 'home'
}
</script><style scoped></style>

3、router路由

3.1 routes.js代码

import login from '@/view/login/login'
// import HelloWorld from '@/components/HelloWorld'
import Main from '@/components/main/main'
import home from '@/view/single-page/home/home'/*** iview-admin中meta除了原生参数外可配置的参数:* meta: {*  title: { String|Number|Function }*         显示在侧边栏、面包屑和标签栏的文字*         使用'{{ 多语言字段 }}'形式结合多语言使用,例子看多语言的路由配置;*         可以传入一个回调函数,参数是当前路由对象,例子看动态路由和带参路由*  hideInBread: (false) 设为true后此级路由将不会出现在面包屑中,示例看QQ群路由配置*  hideInMenu: (false) 设为true后在左侧菜单不会显示该页面选项*  notCache: (false) 设为true后页面在切换标签后不会缓存,如果需要缓存,无需设置这个字段,而且需要设置页面组件name属性和路由配置的name一致*  access: (null) 可访问该页面的权限数组,当前路由设置的权限会影响子路由*  icon: (-) 该页面在左侧菜单、面包屑和标签导航处显示的图标,如果是自定义图标,需要在图标名称前加下划线'_'*  beforeCloseName: (-) 设置该字段,则在关闭当前tab页时会去'@/router/before-close.js'里寻找该字段名对应的方法,作为关闭前的钩子函数* }*/export default [{path: '/login',name: 'login',meta: {title: 'Login - 登录',hideInMenu: true},component: login},{path: '/',name: '_home',redirect: '/home',component: Main,meta: {hideInMenu: true,notCache: true},children: [{path: '/home',name: 'home',meta: {hideInMenu: true,title: '首页',notCache: true,icon: 'md-home'},component: home}]}
]

3.2 index.js代码

import routes from './routers'
import Vue from 'vue'
import Router from 'vue-router'
import iView from 'view-design'
import store from '@/store'
import { setToken, getToken, canTurnTo, setTitle } from "@/libs/util";
import config from '@/config'
const { homeName } = configVue.use(Router)const LOGIN_PAGE_NAME = 'login'
const router = new Router({routes,mode: 'history'
})
const turnTo = (to, access, next) => {if (canTurnTo(to.name, access, routes)) {next() // 有权限,可访问} else {next({ replace: true, name: 'error_401' }) // 无权限,重定向到401页面}
}router.beforeEach((to, from, next) => {iView.LoadingBar.start()const token = getToken()if (!token && to.name !== LOGIN_PAGE_NAME) {// 未登录且要跳转的页面不是登录页next({name: LOGIN_PAGE_NAME // 跳转到登录页})} else if (!token && to.name === LOGIN_PAGE_NAME) {// 未登录且要跳转的页面是登录页next() // 跳转} else if (token && to.name === LOGIN_PAGE_NAME) {// 已登录且要跳转的页面是登录页next({name: homeName // 跳转到homeName页})} else {if (store.state.user.hasGetInfo) {turnTo(to, store.state.user.access, next)} else {store.dispatch('getUserInfo').then(user => {// 拉取用户信息,通过用户权限和跳转的页面的name来判断是否有权限访问;access必须是一个数组,如:['super_admin'] ['super_admin', 'admin']turnTo(to, user.access, next)}).catch(() => {setToken('')next({name: 'login'})})}}
})router.afterEach(to => {setTitle(to, router.app)iView.LoadingBar.finish()window.scrollTo(0, 0)
})export default router

3.3 libs文件夹下:util.js,tools.js

util.js中setToken, getToken, canTurnTo, setTitle方法代码如下:

import Cookies from 'js-cookie'
import { hasOneOf } from '@/libs/tools'export const TOKEN_KEY = 'token'export const setToken = (token) => {Cookies.set(TOKEN_KEY, token, { expires: cookieExpires || 1 })
}export const getToken = () => {const token = Cookies.get(TOKEN_KEY)if (token) {return token} else {return false}
}/*** @param {*} access 用户权限数组,如 ['super_admin', 'admin']* @param {*} route 路由列表*/
const hasAccess = (access, route) => {if (route.meta && route.meta.access) {return hasOneOf(access, route.meta.access)} else {return true}
}/*** 权鉴* @param {*} name 即将跳转的路由name* @param {*} access 用户权限数组* @param {*} routes 路由列表* @description 用户是否可跳转到该页*/
export const canTurnTo = (name, access, routes) => {const routePermissionJudge = (list) => {return list.some(item => {if (item.children && item.children.length) {return routePermissionJudge(item.children)} else if (item.name === name) {return hasAccess(access, item)}})}return routePermissionJudge(routes)
}export const getRouteTitleHandled = (route) => {let router = { ...route }let meta = { ...route.meta }let title = ''if (meta.title) {if (typeof meta.title === 'function') {meta.__titleIsFunction__ = truetitle = meta.title(router)} else title = meta.title}meta.title = titlerouter.meta = metareturn router
}export const showTitle = (item, vm) => {let { title, __titleIsFunction__ } = item.metaif (!title) returnif (useI18n) {if (title.includes('{{') && title.includes('}}') && useI18n) title = title.replace(/({{[\s\S]+?}})/, (m, str) => str.replace(/{{([\s\S]*)}}/, (m, _) => vm.$t(_.trim())))else if (__titleIsFunction__) title = item.meta.titleelse title = vm.$t(item.name)} else title = (item.meta && item.meta.title) || item.namereturn title
}/*** @description 根据当前跳转的路由设置显示在浏览器标签的title* @param {Object} routeItem 路由对象* @param {Object} vm Vue实例*/
export const setTitle = (routeItem, vm) => {const handledRoute = getRouteTitleHandled(routeItem)const pageTitle = showTitle(handledRoute, vm)const resTitle = pageTitle ? `${title} - ${pageTitle}` : titlewindow.document.title = resTitle
}

tools.js中hasOneOf方法代码如下:

/*** @param {Array} target 目标数组* @param {Array} arr 需要查询的数组* @description 判断要查询的数组是否至少有一个元素包含在目标数组中*/
export const hasOneOf = (targetarr, arr) => {return targetarr.some(_ => arr.indexOf(_) > -1)
}

安装Cookie

Terminal中输入

cnpm install js-cookie --save

util.js中输入

import Cookie from 'js-cookie'

报错TypeError: vm.$t is not a function

解决方案:先安装vue-i18n,再将$t改成_t

安装vue-i18n

Terminal中输入:

cnpm install vue-i18n --save

src文件夹 => locale文件夹 => index.js,lang文件夹

index.js代码如下:

import Vue from 'vue'
import VueI18n from 'vue-i18n'
import customZhCn from './lang/zh-CN'
import customZhTw from './lang/zh-TW'
import customEnUs from './lang/en-US'
import zhCnLocale from 'view-design/src/locale/lang/zh-CN'
import enUsLocale from 'view-design/src/locale/lang/en-US'
import zhTwLocale from 'view-design/src/locale/lang/zh-TW'
import { localRead } from '@/libs/util'Vue.use(VueI18n)// 自动根据浏览器系统语言设置语言
const navLang = navigator.language
const localLang = (navLang === 'zh-CN' || navLang === 'en-US') ? navLang : false
let lang = localLang || localRead('local') || 'zh-CN'Vue.config.lang = lang// vue-i18n 6.x+写法
Vue.locale = () => {}
const messages = {'zh-CN': Object.assign(zhCnLocale, customZhCn),'zh-TW': Object.assign(zhTwLocale, customZhTw),'en-US': Object.assign((enUsLocale, customEnUs))
}
const i18n = new VueI18n({locale: lang,messages
})export default i18n

libs文件夹下 util.js中localRead方法:

export const localRead = (key) => {return localStorage.getItem(key) || ''
}

lang文件夹 => en-US.js,zh-CN.js,zh-TW.js

# en-US.js代码
export default {home: 'Home',login: 'Login',components: 'Components',count_to_page: 'Count-to',tables_page: 'Table',split_pane_page: 'Split-pane',markdown_page: 'Markdown-editor',editor_page: 'Rich-Text-Editor',icons_page: 'Custom-icon',img_cropper_page: 'Image-editor',update: 'Update',doc: 'Document',join_page: 'QQ Group',update_table_page: 'Update .CSV',update_paste_page: 'Paste Table Data',multilevel: 'multilevel',directive_page: 'Directive',level_1: 'Level-1',level_2: 'Level-2',level_2_1: 'Level-2-1',level_2_3: 'Level-2-3',level_2_2: 'Level-2-2',level_2_2_1: 'Level-2-2-1',level_2_2_2: 'Level-2-2-2',excel: 'Excel','upload-excel': 'Upload Excel','export-excel': 'Export Excel',tools_methods_page: 'Tools Methods',drag_list_page: 'Drag-list',i18n_page: 'Internationalization',modalTitle: 'Modal Title',content: 'This is the modal box content.',buttonText: 'Show Modal','i18n-tip': 'Note: Only this page is multi-language, other pages do not add language content to the multi-language package.',error_store_page: 'Error Collection',error_logger_page: 'Error Logger',query: 'Query',params: 'Params',cropper_page: 'Cropper',message_page: 'Message Center',tree_table_page: 'Tree Table',org_tree_page: 'Org Tree',drag_drawer_page: 'Draggable Drawer',tree_select_page: 'Tree Selector'
}
# zh-CN.js代码
export default {home: '首页',login: '登录',components: '组件',count_to_page: '数字渐变',tables_page: '多功能表格',split_pane_page: '分割窗口',markdown_page: 'Markdown编辑器',editor_page: '富文本编辑器',icons_page: '自定义图标',img_cropper_page: '图片编辑器',update: '上传数据',join_page: 'QQ群',doc: '文档',update_table_page: '上传CSV文件',update_paste_page: '粘贴表格数据',multilevel: '多级菜单',directive_page: '指令',level_1: 'Level-1',level_2: 'Level-2',level_2_1: 'Level-2-1',level_2_3: 'Level-2-3',level_2_2: 'Level-2-2',level_2_2_1: 'Level-2-2-1',level_2_2_2: 'Level-2-2-2',excel: 'Excel','upload-excel': '上传excel','export-excel': '导出excel',tools_methods_page: '工具函数',drag_list_page: '拖拽列表',i18n_page: '多语言',modalTitle: '模态框题目',content: '这是模态框内容',buttonText: '显示模态框','i18n-tip': '注:仅此页做了多语言,其他页面没有在多语言包中添加语言内容',error_store_page: '错误收集',error_logger_page: '错误日志',query: '带参路由',params: '动态路由',cropper_page: '图片裁剪',message_page: '消息中心',tree_table_page: '树状表格',org_tree_page: '组织结构树',drag_drawer_page: '可拖动抽屉',tree_select_page: '树状下拉选择器'
}
# zh-TW.js代码
export default {home: '首頁',login: '登錄',components: '组件',count_to_page: '数字渐变',tables_page: '多功能表格',split_pane_page: '分割窗口',markdown_page: 'Markdown編輯器',editor_page: '富文本編輯器',icons_page: '自定義圖標',img_cropper_page: '圖片編輯器',update: '上傳數據',join_page: 'QQ群',doc: '文檔',update_table_page: '上傳CSV文件',update_paste_page: '粘貼表格數據',multilevel: '多级菜单',directive_page: '指令',level_1: 'Level-1',level_2: 'Level-2',level_2_1: 'Level-2-1',level_2_3: 'Level-2-3',level_2_2: 'Level-2-2',level_2_2_1: 'Level-2-2-1',level_2_2_2: 'Level-2-2-2',excel: 'Excel','upload-excel': '上傳excel','export-excel': '導出excel',tools_methods_page: '工具函數',drag_list_page: '拖拽列表',i18n_page: '多語言',modalTitle: '模態框題目',content: '這是模態框內容',buttonText: '顯示模態框','i18n-tip': '注:僅此頁做了多語言,其他頁面沒有在多語言包中添加語言內容',error_store_page: '錯誤收集',error_logger_page: '錯誤日誌',query: '帶參路由',params: '動態路由',cropper_page: '圖片裁剪',message_page: '消息中心',tree_table_page: '樹狀表格',org_tree_page: '組織結構樹',drag_drawer_page: '可拖動抽屜',tree_select_page: '樹狀下拉選擇器'
}

main.js中添加:

import i18n from '@/locale'// Vue.use(i18n)
Vue.use(ViewUI, {i18n: (key, value) => i18n.t(key, value)
})new Vue({el: '#app',i18n,router,store,components: { App },template: '<App/>'
})

终于跳转成功,登录后跳转到 home页面,如下

四、以上出现的若干 JavaScript 语法和 Vue 的相关使用解析

1、props相关

login-form.vue中代码:

props: {userNameRules: {// 此处省略},passwordRules: {// 此处省略}}

prop是子组件用来接受父组件传递过来的数据的一个自定义属性。
父组件的数据需要通过props把数据传给子组件,而子组件需要显式地使用props选项声明prop。

2、vue组件间传值ref、prop和emit

login-form.vue中代码:

<Form ref="loginForm"><FormItem prop="userName"></FormItem><FormItem prop="password"></FormItem><FormItem><Button type="primary">登录</Button></FormItem></Form>

prop:父组件向子组件动态传值。
着重于数据的传递,它并不能调用子组件里的属性和方法。
像创建文章组件时,自定义标题和内容这样的使用场景,最适合使用prop。

ref:父组件中调用子组件的方法并向子组件传参。
着重于索引,主要用来调用子组件里的属性和方法,其实并不擅长数据传递。而且ref用在dom元素的时候,能使到选择器的作用,这个功能比作为索引更常有用到,比如以上调用时使用this.$ref.loginForm

emit:子组件触发父组件的方法并传递参数

3、computed计算属性

login-form.vue中代码:

computed: {rules () {return {userName: this.userNameRules,password: this.passwordRules}}}

计算属性是基于它们的响应式依赖进行缓存的。只在相关响应式依赖发生改变时它们才会重新求值。
只要userNameRules 和passwordRules没有发生改变,多次访问rules计算属性会立即返回之前的计算结果,而不必再次执行。如果是函数则不必再次执行函数,可以提高性能。

4、@keydown.enter.native监听键盘的回车事件

keydown:用户摁下摁键时发生
keypress:用户摁下摁键,并且产生一个字符时发生
keyup: 用户释放某一个摁键时触发

总结

本文主要讲了 iview-admin中登录页面布局及登录逻辑的实现,下一篇将介绍main页面布局。

上一篇文章地址:iview-admin源码分析(二):创建Vue.js项目+引入IView

iview-admin源码分析(三):登录页面及逻辑实现相关推荐

  1. Spring源码分析(三)

    Spring源码分析 第三章 手写Ioc和Aop 文章目录 Spring源码分析 前言 一.模拟业务场景 (一) 功能介绍 (二) 关键功能代码 (三) 问题分析 二.使用ioc和aop重构 (一) ...

  2. Nouveau源码分析(三):NVIDIA设备初始化之nouveau_drm_probe

    Nouveau源码分析(三) 向DRM注册了Nouveau驱动之后,内核中的PCI模块就会扫描所有没有对应驱动的设备,然后和nouveau_drm_pci_table对照. 对于匹配的设备,PCI模块 ...

  3. 【投屏】Scrcpy源码分析三(Client篇-投屏阶段)

    Scrcpy源码分析系列 [投屏]Scrcpy源码分析一(编译篇) [投屏]Scrcpy源码分析二(Client篇-连接阶段) [投屏]Scrcpy源码分析三(Client篇-投屏阶段) [投屏]Sc ...

  4. ABP源码分析三十四:ABP.Web.Mvc

    ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用. 第二,一些常见的基础 ...

  5. 【转】ABP源码分析三十五:ABP中动态WebAPI原理解析

    动态WebAPI应该算是ABP中最Magic的功能之一了吧.开发人员无须定义继承自ApiController的类,只须重用Application Service中的类就可以对外提供WebAPI的功能, ...

  6. 【转】ABP源码分析三十四:ABP.Web.Mvc

    ABP.Web.Mvc模块主要完成两个任务: 第一,通过自定义的AbpController抽象基类封装ABP核心模块中的功能,以便利的方式提供给我们创建controller使用. 第二,一些常见的基础 ...

  7. 【转】ABP源码分析三十三:ABP.Web

    ABP.Web模块并不复杂,主要完成ABP系统的初始化和一些基础功能的实现. AbpWebApplication : 继承自ASP.Net的HttpApplication类,主要完成下面三件事 一,在 ...

  8. 【转】ABP源码分析三十一:ABP.AutoMapper

    这个模块封装了Automapper,使其更易于使用. 下图描述了改模块涉及的所有类之间的关系. AutoMapAttribute,AutoMapFromAttribute和AutoMapToAttri ...

  9. 【转】ABP源码分析三:ABP Module

    Abp是基于模块化设计思想进行构建的.开发人员可以将自定义的功能以模块(module)的形式集成到ABP中.具体的功能都可以设计成一个单独的Module.Abp底层框架提供便捷的方法集成每个Modul ...

  10. Spring 源码分析(三) —— AOP(五)创建代理

    2019独角兽企业重金招聘Python工程师标准>>> 创建代理 代理的定义其实非常简单,就是改变原来目标对象方法调用的运行轨迹.这种改变,首先会对这些方法进行拦截,从而为这些方法提 ...

最新文章

  1. Apache HTTP Server Version 2.2 文档中文版
  2. java.lang.NoSuchFieldError: deferredExpression
  3. 数字电路技术基础一二章
  4. mac 如何查看anaconda的路径_Mac OS如何直接查看gif图片?分享MAC直接查看gif图片的三种方法...
  5. 1 个闭环 + 1 个案例,为你解读实现数据驱动的秘诀
  6. 【机器学习入门】深入浅出聚类算法!如何对王者英雄聚类分析,探索英雄之间的秘密...
  7. s:property的用法
  8. Redis安装部署配置说明
  9. 开发基础(字符编码、列表操作)
  10. 得到选择框句柄 怎么操作_知道借名买房有风险,只能选择借名买房该怎么操作?...
  11. ajax拼接外部变量,在ajax调用中访问函数外部变量的问题
  12. LINQPad工具-linq、sql、IL优化和转换
  13. 4a怎么打开sqlserver_百元级别荣耀路由X3和小米路由4A,哪款真的香?
  14. 40行代码的人脸识别实践【转】
  15. Flutter PageView简析
  16. Qt实现界面滑动切换效果
  17. 机械设计基础课程设计详细步骤(说明书)
  18. lj245a引脚功能图_74HC245引脚图应用电路与中文资料
  19. foxmail超大附件服务器文件怎么删,foxmail邮件太大怎么发?foxmail发送超大附件的方法...
  20. 【毕业设计】大数据 电影数据分析与可视化系统 - python Django 大数据 可视化

热门文章

  1. 计算机大赛电子杂志,电子竞技杂志
  2. 关于Repeater 嵌套梆定不明之处
  3. hellgate 射击体验
  4. trie php,php-优化Trie实现
  5. 20个经典的Java代码片段
  6. readLine()与read()
  7. 大学时代的牺牲品,测试工程师转型测试开发历程...
  8. 品牌策划的关键需要细化产品的核心卖点!
  9. 蓝桥杯 蚂蚁感冒(Java)
  10. synchronized在JDK6做了哪些优化