iview admin源码解读
下载地址:iView Admin 码云:https://gitee.com/icarusion/iview-admin下载后的目录结构登录页显示效果首页显示效果:在此介绍一下iview的框架代码如何阅读main.js里引入了很多要用到的插件和组件import Vue from 'vue'import App from './App'import router...
- 下载地址:
iView Admin 码云:https://gitee.com/icarusion/iview-admin
- 下载后的目录结构
- 登录页显示效果
- 首页显示效果:
在此介绍一下iview的框架代码如何阅读
- main.js里引入了很多要用到的插件和组件
import Vue from 'vue'
import App from './App'
import router from './router'
import store from './store'
import iView from 'iview'
import i18n from '@/locale'
import config from '@/config'
import importDirective from '@/directive'
import { directive as clickOutside } from 'v-click-outside-x'
import installPlugin from '@/plugin'
import './index.less'
import '@/assets/icons/iconfont.css'
import TreeTable from 'tree-table-vue'
import VOrgTree from 'v-org-tree'
import 'v-org-tree/dist/v-org-tree.css'
其中iview,router,store,App就是我们要用到的关键插件
- 在文件的最后,把router路由传递进来:
new Vue({
el: '#app',
router,
i18n,
store,
render: h => h(App)
})
由import router from './router’这行代码可以看到router所在的目录,跟main.js在同一级,
对应的文件就是router/routers.js
此文件导出一个对象,此文件里有大量的数据,就是框架左侧菜单的数据
```javascript
{
path: '/login',
name: 'login',
meta: {
title: 'Login - 登录',
hideInMenu: true
},
component: () => import('@/view/login/login.vue')
},
{
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: () => import('@/view/single-page/home')
}
]
},
从下面这段代码可以知道,框架第一个加载的组件就是App.vue
```javascript
new Vue({
el: '#app',
router,
i18n,
store,
render: h => h(App)
})
App.vue里是什么代码呢?
<template>
<div id="app">
<router-view/>
</div>
</template>
从此段代码看出,这里放了一个路由视图,可能会有点疑惑,这样放有什么好处呢?
好处就是当我点击某个页面进行切换时,浏览器会出现一个蓝色的加载条,如下图所示:
当从地址栏访问此工程时,输入任意地址,都会跳转到login.vue中。
为什么会跳到这个页面呢,那是因为router/index.js里有这么一段路由守卫的代码:
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'
})
})
}
}
})
此代码会使用getToken()方法获取当前是否已经登录过,
那么是从哪里来的呢?
在顶部有一段代码:import { setToken, getToken, canTurnTo, setTitle } from ‘@/libs/util’
可以看到这个方法是从/libs/util.js里来的。
export const TOKEN_KEY = 'token'
export const getToken = () => {
const token = Cookies.get(TOKEN_KEY)
if (token) return token
else return false
}
从代码可以看出,cookie里应该有一个叫token的cookie对象
从Chrome浏览器里可以看到这个token,它的值就是我们登录的用户名。
如果用户未登录,getToken方法就会返回一个false,否则就会返回super_admin这个用户名。
回到上面router/index.js文件里的代码可以看到,如果getToken返回false,就会使用路由对象的方法next,跳转到用户登录页
next({
name: LOGIN_PAGE_NAME // 跳转到登录页
})
如果getToken返回用户名,就会跳转到登录页
next({
name: homeName // 跳转到homeName页
})
登录页面是哪个呢?要从路由中找出来
{
path: '/login',
name: 'login',
meta: {
title: 'Login - 登录',
hideInMenu: true
},
component: () => import('@/view/login/login.vue')
},
找到/view/login/login.vue这个页面
<script>
import LoginForm from '_c/login-form'
import { mapActions } from 'vuex'
export default {
components: {
LoginForm
},
methods: {
...mapActions([
'handleLogin',
'getUserInfo'
]),
handleSubmit ({ userName, password }) {
this.handleLogin({ userName, password }).then(res => {
this.getUserInfo().then(res => {
this.$router.push({
name: this.$config.homeName
})
})
})
}
}
}
</script>
当用户点击登录时,会执行handleSubmit 这个方法
比方法又调用了this.handleLogin和getUserInfo这两个方法,这两个方法就是vuex这个插件定义的。
这个文件导入了import { mapActions } from ‘vuex’
vuex的代码及配置是在下面这个目录/store/index.js
其中module子文件夹中的app.js和user.js是vuex的两个子模块module
在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
}
})
所以代码都在两个子模块的js里。在user.js里有handleLogin方法
// 登录
handleLogin ({ commit }, { userName, password }) {
userName = userName.trim()
return new Promise((resolve, reject) => {
login({
userName,
password
}).then(res => {
const data = res.data
commit('setToken', data.token)
resolve()
}).catch(err => {
reject(err)
})
})
},
此方法接收userName, password参数。
先对useName这个参数去除空格,接着执行login方法,login方法是从哪里来的呢?
import {
login,
logout,
getUserInfo,
getMessage,
getContentByMsgId,
hasRead,
removeReaded,
restoreTrash,
getUnreadCount
} from '@/api/user'
从顶部的import中可以看出是来自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'
})
}
上面定义login方法的代码段,可以看出,要用axios发起一个请示,还是post方式发起。目标文件就是login这个地址。而这个地址就是在路由里定义的src\view\login\login.vuelogin.vue。真的发起请求了吗?真的发起了,但是有没有做处理呢,还真是什么都没有干,执行完了这个请求,代码还要往下跑(此得不确定到底有没有做什么,需要再次确认)。
在then方法里执行 commit(‘setToken’, data.token),这就是要调用vuex框架里的setToken方法,此方法在use.js中引入了util.js的方法
export const TOKEN_KEY = 'token'
export const setToken = (token) => {
Cookies.set(TOKEN_KEY, token, { expires: cookieExpires || 1 })
}
这里看出,在浏览器中设置了一个cookie,cookie的名字是token,值是,并且有效期为1天。
此处的token变量其实就是用户名,那么这个用户名是哪里来的呢?其实是在login.vue里传递过来的
handleSubmit ({ userName, password }) {
this.handleLogin({ userName, password }).then(res => {
this.getUserInfo().then(res => {
this.$router.push({
name: this.$config.homeName
})
})
})
}
这段代码的userName参数,传递给了handleLogin方法。
这时进入到this.getUserInfo方法里这个方法也在user.js里。
// 获取用户相关信息
getUserInfo ({ state, commit }) {
return new Promise((resolve, reject) => {
try {
getUserInfo(state.token).then(res => {
const data = res.data
commit('setAvatar', data.avatar)
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)
}
})
},
在getUserInfo 里又返回了一个promise对象,此对象里的代码又使用了getUserInfo方法,有一个state.token的参数,这个参数其实又是一个方法token: getToken(),
这个方法又是从import { setToken, getToken } from ‘@/libs/util’
这段代码可以看出,来自libs/util.js文件
export const getToken = () => {
const token = Cookies.get(TOKEN_KEY)
if (token) return token
else return false
}
完成了登录,
handleSubmit ({ userName, password }) {
this.handleLogin({ userName, password }).then(res => {
this.getUserInfo().then(res => {
this.$router.push({
name: this.$config.homeName
})
})
})
}
name: this.
c
o
n
f
i
g
.
h
o
m
e
N
a
m
e
,
这
段
代
码
就
要
进
行
路
由
跳
转
,
t
h
i
s
.
config.homeName,这段代码就要进行路由跳转,this.
config.homeName,这段代码就要进行路由跳转,this.config.homeName是来自哪里呢?
这时要回到main.js里查看代码
import config from '@/config'
/**
* @description 全局注册应用配置
*/
Vue.prototype.$config = config
正是来自config/index.js文件
/**
* @description 默认打开的首页的路由name值,默认为home
*/
homeName: 'home',
此home可不是home.vue这个文件,还要在路由文件routers.js里确认
{
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: () => import('@/view/single-page/home')
}
]
},
这个home其实就是Main.vue,Main.vue里又有一个子路由,子路由的页面才是home.vue
这时我们要先看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'}">
<side-menu accordion ref="sideMenu" :active-name="$route.name" :collapsed="collapsed" @on-select="turnToPage" :menu-list="menuList">
<!-- 需要放在菜单上面的内容,如Logo,写在side-menu标签内部,如下 -->
<div class="logo-con">
<img v-show="!collapsed" :src="maxLogo" key="max-logo" />
<img v-show="collapsed" :src="minLogo" key="min-logo" />
</div>
</side-menu>
</Sider>
<Layout>
<Header class="header-con">
<header-bar :collapsed="collapsed" @on-coll-change="handleCollapsedChange">
<user :message-unread-count="unreadCount" :user-avatar="userAvatar"/>
<language v-if="$config.useI18n" @on-lang-change="setLocal" style="margin-right: 10px;" :lang="local"/>
<error-store v-if="$config.plugin['error-store'] && $config.plugin['error-store'].showInHeader" :has-read="hasReadErrorPage" :count="errorCount"></error-store>
<fullscreen v-model="isFullscreen" style="margin-right: 10px;"/>
</header-bar>
</Header>
<Content class="main-content-con">
<Layout class="main-layout-con">
<div class="tag-nav-wrapper">
<tags-nav :value="$route" @input="handleClick" :list="tagNavList" @on-close="handleCloseTag"/>
</div>
<Content class="content-wrapper">
<keep-alive :include="cacheList">
<router-view/>
</keep-alive>
<ABackTop :height="100" :bottom="80" :right="50" container=".content-wrapper"></ABackTop>
</Content>
</Layout>
</Content>
</Layout>
</Layout>
</template>
main.vue里有一个Sider组件,传递的:menu-list="menuList"参数是menuList,这就是我们看到的左侧的导航。
computed: {
menuList () {
return this.$store.getters.menuList
}
}
这正是vuex的用法,在计算方法里有一个menuList ,而它来自app.js里的代码:
getters: {
menuList: (state, getters, rootState) => getMenuByRouter(routers, rootState.user.access),
errorCount: state => state.errorList.length
},
参数routers是从import routers from '@/router/routers’导入进来的。
而routers就是整个路由对象。
getMenuByRouter方法又从libs/util.js里引入。
/**
* @param {Array} list 通过路由列表得到菜单列表
* @returns {Array}
*/
export const getMenuByRouter = (list, access) => {
let res = []
forEach(list, item => {
if (!item.meta || (item.meta && !item.meta.hideInMenu)) {
let obj = {
icon: (item.meta && item.meta.icon) || '',
name: item.name,
meta: item.meta
}
if ((hasChild(item) || (item.meta && item.meta.showAlways)) && showThisMenuEle(item, access)) {
obj.children = getMenuByRouter(item.children, access)
}
if (item.meta && item.meta.href) obj.href = item.meta.href
if (showThisMenuEle(item, access)) res.push(obj)
}
})
return res
}
list其实就是我们传递进来的路由对象,路由对象里有每多元素,都如下代码所示,有path,name,meta,component,children。跟上面代码可以对应起来。
{
path: '/join',
name: 'join',
component: Main,
meta: {
hideInBread: true
},
children: [
{
path: 'join_page',
name: 'join_page',
meta: {
icon: '_qq',
title: 'QQ群'
},
component: () => import('@/view/join-page.vue')
}
]
},
总结:iview admin的源码还是比较复杂的。一眼看下去,有点不好理解。
更多推荐
所有评论(0)