vue-admin-template
vue-element-admin结构
├── build # 构建相关
├── mock # 项目mock 模拟数据
├── public # 静态资源
│ │── favicon.ico # favicon图标
│ └── index.html # html模板
├── src # 源代码
│ ├── api # 所有请求
│ ├── assets # 主题 字体等静态资源
│ ├── components # 全局公用组件
│ ├── icons # 项目所有 svg icons
│ ├── layout # 全局 layout
│ ├── router # 路由
│ ├── store # 全局 store管理
│ ├── styles # 全局样式
│ ├── utils # 全局公用方法
│ ├── vendor # 公用vendor
│ ├── views # views 所有页面
│ ├── App.vue # 入口页面
│ ├── main.js # 入口文件 加载组件 初始化等
│ └── permission.js # 权限管理
│ └── settings.js # 配置文件
├── tests # 测试
├── .env.xxx # 环境变量配置
├── .eslintrc.js # eslint 配置项
├── .babelrc # babel-loader 配置
├── .travis.yml # 自动化CI配置
├── vue.config.js # vue-cli 配置
├── postcss.config.js # postcss 配置
└── package.json # package.json
安装
# 进入项目目录
cd vue-admin-template
# 安装依赖
npm install
# 本地开发 启动项目
npm run dev
登录表单验证
login页面的html部分
<template>
<div class="login-container">
<el-form
ref="loginForm"
:model="loginForm"
:rules="loginRules"
class="login-form"
auto-complete="on"
label-position="left"
>
<div class="title-container">
<h3 class="title">小优后台电商管理系统</h3>
</div>
<el-form-item prop="username">
<span class="svg-container">
<svg-icon icon-class="user" />
</span>
<el-input
ref="username"
v-model="loginForm.username"
v-focus
placeholder="Username"
name="username"
type="text"
tabindex="1"
auto-complete="on"
autofocus
/>
</el-form-item>
<el-form-item prop="password">
<span class="svg-container">
<svg-icon icon-class="password" />
</span>
<el-input
:key="passwordType"
ref="password"
v-model="loginForm.password"
:type="passwordType"
placeholder="Password"
name="password"
tabindex="2"
auto-complete="on"
@keyup.enter.native="handleLogin"
/>
<!-- 按键修饰符 enter:表示摁下回车键的时候 -->
<span class="show-pwd" @click="showPwd">
<svg-icon
:icon-class="passwordType === 'password' ? 'eye' : 'eye-open'"
/>
</span>
</el-form-item>
<el-button
:loading="loading"
type="primary"
style="width: 100%; margin-bottom: 30px"
@click.native.prevent="handleLogin"
>登录</el-button
>
<!-- 事件修饰符 prevent:阻止事件的默认行为 -->
<div class="tips">
<span style="margin-right: 20px">username: admin</span>
<span> password: any</span>
</div>
</el-form>
</div>
</template>
<script>
import { validUsername } from '@/utils/validate'
export default {
name: 'Login',
data () {
const validateUsername = (rule, value, callback) => {
if (!validUsername(value)) {
callback(new Error('请输入正确的用户名'))
} else {
callback()
}
}
return {
loginForm: {
username: 'admin',
password: '123456'
},
loginRules: {
username: [{ required: true, trigger: 'blur', validator: validateUsername }],
password: [
{ required: true, trigger: 'blur' },
{ min: 6, max: 12, trigger: 'blur', message: '密码长度应该在6-12位之间' }
]
},
loading: false,
passwordType: 'password',
redirect: undefined
}
},
methods: {
// eslint-disable-next-line space-before-function-paren
showPwd () {
if (this.passwordType === 'password') {
this.passwordType = ''
} else {
this.passwordType = 'password'
}
this.$nextTick(() => {
this.$refs.password.focus()
})
},
// eslint-disable-next-line space-before-function-paren
async handleLogin () {
try {
await this.$refs.loginForm.validate()
this.loading = true
// dispatch 调用 action
await this.$store.dispatch('user/login', this.loginForm)
console.log('login组件继续执行')
// 登陆成功之后跳转到
this.$router.push({ path: '/' })
this.loading = false
} catch (err) {
console.log(err)
this.loading = false
return false
}
}
}
}
</script>
<style lang="scss">
/* 修复input 背景不协调 和光标变色 */
/* Detail see https://github.com/PanJiaChen/vue-element-admin/pull/927 */
$bg: #283443;
$light_gray: #fff;
$cursor: #fff;
@supports (-webkit-mask: none) and (not (cater-color: $cursor)) {
.login-container .el-input input {
color: $cursor;
}
}
/* reset element-ui css */
.login-container {
.el-input {
display: inline-block;
height: 47px;
width: 85%;
input {
background: transparent;
border: 0px;
-webkit-appearance: none;
border-radius: 0px;
padding: 12px 5px 12px 15px;
color: $light_gray;
height: 47px;
caret-color: $cursor;
&:-webkit-autofill {
box-shadow: 0 0 0px 1000px $bg inset !important;
-webkit-text-fill-color: $cursor !important;
}
}
}
.el-form-item {
border: 1px solid rgba(255, 255, 255, 0.1);
background: rgba(0, 0, 0, 0.1);
border-radius: 5px;
color: #454545;
}
}
</style>
<style lang="scss" scoped>
$bg: #2d3a4b;
$dark_gray: #889aa4;
$light_gray: #eee;
.login-container {
min-height: 100%;
width: 100%;
background-color: $bg;
overflow: hidden;
.login-form {
position: relative;
width: 520px;
max-width: 100%;
padding: 160px 35px 0;
margin: 0 auto;
overflow: hidden;
}
.tips {
font-size: 14px;
color: #fff;
margin-bottom: 10px;
span {
&:first-of-type {
margin-right: 16px;
}
}
}
.svg-container {
padding: 6px 5px 6px 15px;
color: $dark_gray;
vertical-align: middle;
width: 30px;
display: inline-block;
}
.title-container {
position: relative;
.title {
font-size: 26px;
color: $light_gray;
margin: 0px auto 40px auto;
text-align: center;
font-weight: bold;
}
}
.show-pwd {
position: absolute;
right: 10px;
top: 7px;
font-size: 16px;
color: $dark_gray;
cursor: pointer;
user-select: none;
}
}
</style>
el-form 的属性 :*rules*="rules" 后面这个rules在data里面定义各种规则,然后把规则再给el-form-item的prop属性对应上
然后验证的时候 this.$refs.form.**validate**(),form指的是表单上的ref属性绑定的
表单验证部分
最后点击登录进行表单验证,存储token,实现登录
api调用
主页的token拦截处理
根据token处理主页的访问权限问题
流程图
代码:
import router from './router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import getPageTitle from '@/utils/get-page-title'
import store from './store'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
// eslint-disable-next-line space-before-function-paren
router.beforeEach(async (to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const token = getToken()
// console.log(token)
if (token) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done()
} else {
/**
* 静态路由有五个
* 如果 <5,则里面只有静态路由,就要根据用户的权限生成动态路由
* 然后加入到静态路由中
*/
if (store.state.permission.routes.length <= 5) {
// 验证用户是否有访问某个路由的权限
const routes = await store.dispatch('permission/filterRoutes', store.state.user.menus)
// console.log(routes)
router.addRoutes(routes)
/**
* 相当于跳到对应的地址 相当于多做一次跳转 为什么要多做一次跳转
*/
next(to.path)
} else {
next()
}
}
} else {
/* import router from './router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import getPageTitle from '@/utils/get-page-title'
import store from './store'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // no redirect whitelist
// eslint-disable-next-line space-before-function-paren
router.beforeEach(async (to, from, next) => {
// start progress bar
NProgress.start()
// set page title
document.title = getPageTitle(to.meta.title)
// determine whether the user has logged in
const token = getToken()
// console.log(token)
if (token) {
if (to.path === '/login') {
// if is logged in, redirect to the home page
next({ path: '/' })
NProgress.done()
} else {
/**
* 静态路由有五个
* 如果 <5,则里面只有静态路由,就要根据用户的权限生成动态路由
* 然后加入到静态路由中
*/
if (store.state.permission.routes.length <= 5) {
// 验证用户是否有访问某个路由的权限
const routes = await store.dispatch('permission/filterRoutes', store.state.user.menus)
// console.log(routes)
router.addRoutes(routes)
/**
* 相当于跳到对应的地址 相当于多做一次跳转 为什么要多做一次跳转
*/
next(to.path)
} else {
next()
}
}
} else {
/* has no token*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
import router from './router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import getPageTitle from '@/utils/get-page-title'
import store from './store'
NProgress.configure({ showSpinner: false }) // NProgress Configuration
const whiteList = ['/login'] // 白名单
router.beforeEach(async (to, from, next) => {
NProgress.start()
document.title = getPageTitle(to.meta.title)
const token = getToken()
// console.log(token)
if (token) {
// 如果有 token,判断是否请求的是登录页
if (to.path === '/login') {
// 如果请求的是登录页,因为具有 token ,表明已经登录了,就直接跳转主页即可
next({ path: '/' })
NProgress.done()
} else {
/**
* 静态路由有五个
* 如果 <5,则里面只有静态路由,就要根据用户的权限生成动态路由
* 然后加入到静态路由中
*/
if (store.state.permission.routes.length <= 5) {
// 验证用户是否有访问某个路由的权限
const routes = await store.dispatch('permission/filterRoutes', store.state.user.menus)
// console.log(routes)
router.addRoutes(routes)
/**
* 相当于跳到对应的地址 相当于多做一次跳转 为什么要多做一次跳转
*/
next(to.path)
} else {
next()
}
}
} else {
/* 如果没有 token,说明没有登录,则判断用户请求的页面是否在白名单之中*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
*/
if (whiteList.indexOf(to.path) !== -1) {
// in the free login whitelist, go directly
next()
} else {
// other pages that do not have permission to access are redirected to the login page.
next(`/login`)
NProgress.done()
}
}
})
router.afterEach(() => {
// finish progress bar
NProgress.done()
})
Axios拦截器
axios作为网络请求的第三方工具, 可以进行请求和响应的拦截
拦截器的原理
请求拦截器和响应拦截器
import axios from 'axios'
import { Message } from 'element-ui'
import store from '@/store'
import router from '../router'
import { getToken, getTimeSteamp } from '@/utils/auth'
// 定义 token 超时的时间
const timeOut = 3600 * 24 * 7
// axios.defaults.baseURL=
// 创建一个axios实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // url = base url + request url
// withCredentials: true, // send cookies when cross-domain requests
timeout: 5000 // request timeout
})
// request interceptor 注册的请求的拦截器
service.interceptors.request.use(
config => {
// do something before request is sent
// 获取token值且有 token 值怎么做
if (store.getters.token) {
// 获取 token 设置的时间
const tokenTIme = getTimeSteamp()
// 获取当前的时间
const currentTime = Date.now()
// 判断当前 token 的时间戳是否过期
if ((currentTime - tokenTIme) / 1000 > timeOut) {
// store.dispatch('user/logout')
// // 跳转到登录页
// router.push('/login')
// return Promise.reject(new Error('token超时了'))
console.log((currentTime - tokenTIme) / 1000)
}
config.headers['Authorization'] = getToken()
}
return config
},
error => {
// do something with request error
console.log(error) // for debug
return Promise.reject(error)
}
)
// response interceptor 响应拦截器
service.interceptors.response.use(
response => {
const { meta: { msg, status }, data } = response.data
// console.log(res)
// if the custom code is not 20000, it is judged as an error.
if (status !== 200 && status !== 201) {
// 处理 token 过期问题
if (status === 400 && msg === '无效token') {
store.dispatch('user/logout')
router.push('/login')
}
Message({
message: msg || 'Error',
type: 'error',
duration: 5 * 1000
})
return Promise.reject(new Error(msg || 'Error'))
} else {
return data
}
},
error => {
console.log('err' + error) // for debug
Message({
message: error.message,
type: 'error',
duration: 5 * 1000
})
return Promise.reject(error)
}
)
export default service
里面auth里面的方法
vue-element-admin 和 vue-admin-template 有什么区别?
而vue-element-admin是基于element-ui 的一套后台管理系统集成方案。
vue-admin-template是基于vue-element-admin的一套后台管理系统基础模板(最少精简版),可作为模板进行二次开发。
vue-element-admin 是一个后台的集成方案,它囊括了很多的功能和组件,并不适合作为基础模板来进行二次开发。 vue-admin-template 则是一个后台的基础模板脚手架,适合在它的基础上进行二次开发。
RBAC权限系统
在RBAC模型里面,有3个基础组成部分,分别是:用户、角色和权限。
RBAC通过定义角色的权限,并对用户授予某个角色从而来控制用户的权限,实现了用户和权限的逻辑分离(区别于ACL模型),极大地方便了权限的管理
权限验证:
在前后端分离的项目中,权限验证分为三个部分
1)页面级别的权限验证,不同用户登录后,看弹道的左侧菜单是不一样的,又或者在地址栏输入没有的地址,会提示404页面找不到,主要依靠对路由对象的操作实现
2)功能级别的权限验证,根据角色判断你是否有这个功能,有则显示,无则提示权限不够
3)对于后台的API的请求权限,主要由后台完成
User(用户):每个用户都有唯一的UID识别,并被授予不同的角色
Role(角色):不同角色具有不同的权限
Permission(权限):访问权限
用户-角色映射:用户和角色之间的映射关系
角色-权限映射:角色和权限之间的映射
elementElement-UI全局引入和按需引入
网址:
https://element.eleme.cn/#/zh-CN/component/quickstart
完整引入:
在 main.js 中写入以下内容:
import Vue from 'vue';
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
import App from './App.vue';
Vue.use(ElementUI);
new Vue({
el: '#app',
render: h => h(App)
});
以上代码便完成了 Element 的引入。需要注意的是,样式文件需要单独引入。
按需引入:
借助 babel-plugin-component,我们可以只引入需要的组件,以达到减小项目体积的目的。
首先,安装 babel-plugin-component:
npm install babel-plugin-component -D
然后,将 .babelrc 修改为:
{
"presets": [["es2015", { "modules": false }]],
"plugins": [
[
"component",
{
"libraryName": "element-ui",
"styleLibraryName": "theme-chalk"
}
]
]
}
接下来,如果你只希望引入部分组件,比如 Button 和 Select,那么需要在 main.js 中写入以下内容:
import Vue from 'vue';
import { Button, Select } from 'element-ui';
import App from './App.vue';
Vue.use(Button)
Vue.use(Select)
new Vue({
el: '#app',
render: h => h(App)
});
vue全家桶指的是?
vue(整体架构,Vue 是一套用于构建用户界面的渐进式框架)
vuex(状态管理,Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式,适合开发大型单页应用
vue-router(路由)
vue_resource || axios(ajax请求)
如何自定义指令
Vue.directive 自定义指令
filter 过滤器
computed 计算属性
注册一个自定义指令(有全局注册与局部注册)
全局注册注册主要是用Vue.directive方法进行注册
Vue.directive第一个参数是指令的名字(不需要写上v-前缀),第二个参数可以是对象数据,也可以是一个指令函数
el:指令所绑定的元素,可以用来直接操作 DOM
示例代码:
// 注册一个全局自定义指令 `v-focus`
Vue.directive('focus', {
// 当被绑定的元素插入到 DOM 中时……
inserted: function (el) {
// 聚焦元素
el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
}
})
局部注册通过在组件options选项中设置directive属性
directives: {
focus: {
// 指令的定义
inserted: function (el) {
el.focus() // 页面加载完成之后自动让输入框获取到焦点的小功能
}
}
}
然后你可以在模板中任何元素上使用新的 v-focus property,如下:
<input v-focus />