①npm i -D @vue/[email protected]
npm i -D @vue/eslint-config-standard@5.1.0
解决版本依赖 (版本降级命令) npm i -D @vue/[email protected]
成功后将6.1.0降级为5.1.0
②强行解决
这是因为引用资源的路径问题,我们只要在下图的地方修改一下再打包就可以了。
publicPath: './'
下载依赖
npm i
npm i element-ui -S
//全局导入
//main.js
import ElementUI from 'element-ui';
import 'element-ui/lib/theme-chalk/index.css';
Vue.use(ElementUI);
//App.vue
<el-button round>圆角按钮</el-button>
<el-button type="primary" round>主要按钮</el-button>
<el-button type="success" round>成功按钮</el-button>
<el-button type="info" round>信息按钮</el-button>
<el-button type="warning" round>警告按钮</el-button>
<el-button type="danger" round>危险按钮</el-button>
npm install babel-plugin-component -D
//babel.config.js
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
],
plugins: [
[
'component',
{
libraryName: 'element-ui',
styleLibraryName: 'theme-chalk'
}
]
]
}
// 按需导入
//main.js
import 'element-ui/lib/theme-chalk/index.css'
import { Button} from 'element-ui'
Vue.use(Button)
//App.vue
<el-button round>圆角按钮</el-button>
<el-button type="primary" round>主要按钮</el-button>
<el-button type="success" round>成功按钮</el-button>
<el-button type="info" round>信息按钮</el-button>
<el-button type="warning" round>警告按钮</el-button>
<el-button type="danger" round>危险按钮</el-button>
// // 按需导入
import Vue from 'vue'
import {
Button,
Popconfirm,
Avatar,
Breadcrumb,
BreadcrumbItem,
Pagination,
Dialog,
Menu,
Input,
Option,
Table,
TableColumn,
Form,
FormItem,
Icon,
Row,
Col,
Card,
Container,
Header,
Aside,
Main,
Footer,
Link,
Image,
Loading,
MessageBox,
Message,
Drawer,
MenuItem
} from 'element-ui'
import 'element-ui/lib/theme-chalk/index.css'// 样式
Vue.use(Breadcrumb)
Vue.use(BreadcrumbItem)
Vue.use(Drawer)
Vue.use(Popconfirm)
Vue.use(Avatar)
Vue.use(Pagination)
Vue.use(Dialog)
Vue.use(Menu)
Vue.use(MenuItem)
Vue.use(Input)
Vue.use(Option)
Vue.use(Table)
Vue.use(TableColumn)
Vue.use(Form)
Vue.use(FormItem)
Vue.use(Icon)
Vue.use(Row)
Vue.use(Col)
Vue.use(Card)
Vue.use(Container)
Vue.use(Header)
Vue.use(Aside)
Vue.use(Main)
Vue.use(Footer)
Vue.use(Link)
Vue.use(Image)
Vue.use(Loading.directive)
Vue.prototype.$loading = Loading.service
Vue.prototype.$msgbox = MessageBox
Vue.prototype.$alert = MessageBox.alert
Vue.prototype.$confirm = MessageBox.confirm
Vue.prototype.$prompt = MessageBox.prompt
Vue.prototype.$notify = Notification
Vue.prototype.$message = Message
Vue.use(Button)
//main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
// 引入组文件
import '@/utils/element'
Vue.config.productionTip = false
new Vue({
router,
store,
render: h => h(App)
}).$mount('#app')
//theme.scss
/* 改变主题色变量 */
$--color-primary:rgba(114,124,245,1);
/* 改变 icon 字体路径变量,必需 */
$--font-path: '~element-ui/lib/theme-chalk/fonts';
@import "~element-ui/packages/theme-chalk/src/index";
//初始化body样式
body {
margin: 0;
padding: 0;
background: #fafbfe;
}
//main.js
// 引入主题文件
import '@/styles/theme.scss'
const router = new VueRouter({
routes: [
{
path: '/login',
component: () => import('@/views/login/index.vue')
}
]
})
//router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const router = new VueRouter({
routes: [
{
path: '/login',
component: () => import('@/views/login/index.vue')
},
{
path: '/',
component: () => import('@/views/layout/index.vue'),
redirect: '/dashboard',
children: [
{
path: '/article',
component: () => import('@/views/article/index.vue')
},
{
path: '/dashboard',
component: () => import('@/views/dashboard/index.vue')
}
]
}
]
})
export default router
//App.vue配路由出口
<router-view></router-view>
//layout/index.vue
<router-view></router-view>
深度作用选择器:从当前去修改子组件深层穿透样式 less中是/deep/
::v-deep .el-card__header
要实现表单的校验,需要做的事情
{ min: 5, max: 11, message: '长度在5到11个字符', trigger: 'blur' }
{ pattern: /^\w{5,11}$/, message: '密码长度在5到11个字符', trigger: ['blur', 'change'] }
rules: {
// trigger 可以支持两种类型: 字符串 / 字符串的数组
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
// 校验数据的长度范围
// 方法1
{ min: 5, max: 11, message: '长度在5到11个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '密码不能为空', trigger: ['blur', 'change'] },
// 长度校验用法2
// 正则匹配:完全匹配精准匹配,模糊匹配(包含匹配)
{ pattern: /^\w{5,11}$/, message: '密码长度在5到11个字符', trigger: ['blur', 'change'] }
]
}
login/index.vue
//login/index.vue
<template>
<div class="login-page">
<el-card>
<template #header>黑马面经运营后台</template>
<!-- 要实现表单的校验,需要做的事情 -->
<!-- - 要准备好校验规则 -->
<!-- - 要给 el-form绑定 model 和rules -->
<!-- - 需要给 el-form-item 设置prop属性 -->
<el-form autocomplete="off" :model="loginForm" :rules="rules">
<el-form-item label="用户名" prop="username">
<el-input placeholder="输入用户名" v-model="loginForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" placeholder="输入用户密码" v-model="loginForm.password"></el-input>
</el-form-item>
<el-form-item class="tc">
<el-button type="primary">登 录</el-button>
<el-button>重 置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
export default {
name: 'login-page',
data () {
return {
loginForm: {
username: '',
password: ''
},
rules: {
// trigger 可以支持两种类型: 字符串 / 字符串的数组
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
// 校验数据的长度范围
// 方法1
{ min: 5, max: 11, message: '长度在5到11个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '密码不能为空', trigger: ['blur', 'change'] },
// 长度校验用法2
// 正则匹配:完全匹配精准匹配,模糊匹配(包含匹配)
{ pattern: /^\w{5,11}$/, message: '密码长度在5到11个字符', trigger: ['blur', 'change'] }
]
}
}
},
methods: {
}
}
</script>
<style lang="scss" scoped>
.login-page {
min-height: 100vh;
background: url(@/assets/login-bg.svg) no-repeat center / cover;
display: flex;
align-items: center;
justify-content: space-around;
.el-card {
width: 420px;
// 深度作用选择器:从当前去修改子组件深层穿透样式less中是/deep/
::v-deep .el-card__header {
height: 80px;
background: rgba(114, 124, 245, 1);
text-align: center;
line-height: 40px;
color: #fff;
font-size: 18px;
}
}
.el-form {
padding: 0 20px;
}
.tc {
text-align: center;
}
}</style>
this.$refs.form.validate().then(() => {
console.log('校验成功')
// 调用接口,发请求,获取token
// 跳转到管理台首页
}).catch(err => {
console.log(err, '校验失败')
})
this.$refs.form.validate((isok) => {
console.log(isok)
// isok: false ==> 校验失败 ==> 提示用户
// isok true ==>校验成功 ==> 登录流程
// 登录流程是什么?
// 1. 发请求, 换取token
// 2. 跳转到首页 /dashboard
})
对整个表单进行校验的方法,参数为一个回调函数。该回调函数会在校验结束后被调用,并传入两个参数:是否校验成功和未通过校验的字段。若不传入回调函数,则会返回一个 promise
//utils/request.js
import axios from 'axios'
// 创建一个新的请求实例对象
const instance = axios.create({
baseURL: 'http://interview-api-t.itheima.net',
timeout: 1000 * 3
})
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
})
export default instance
//api/user.js
import request from '@/utils/request'
// 处理用户的登录请求
export function userLogin (data) {
return request.post('/auth/login', data)
}
//store/index.js
import { userLogin } from '@/api/user'
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
user: {
// 开启命名空间
namespaced: true,
state: {
token: ''
},
mutations: {
setToken (state, token) {
state.token = token
}
},
actions: {
async loginAction (context, payload) {
// 发请求,换token
// 提交mutations,更新token
const res = await userLogin(payload)
const token = res.data.token
// 设置token到store中
context.commit('setToken', token)
}
}
}
}
})
//utils/request.js
import axios from 'axios'
// 创建一个新的请求实例对象
const instance = axios.create({
baseURL: 'http://interview-api-t.itheima.net',
timeout: 1000 * 3
})
// 添加请求拦截器
instance.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
return config
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error)
})
// 添加响应拦截器
instance.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
return response.data
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error)
})
export default instance
//login/index.js
<template>
<div class="login-page">
<el-card>
<template #header>黑马面经运营后台</template>
<!-- 要实现表单的校验,需要做的事情 -->
<!-- - 要准备好校验规则 -->
<!-- - 要给 el-form绑定 model 和rules -->
<!-- - 需要给 el-form-item 设置prop属性 -->
<el-form ref="form" autocomplete="off" :model="loginForm" :rules="rules">
<el-form-item label="用户名" prop="username">
<el-input placeholder="输入用户名" v-model="loginForm.username"></el-input>
</el-form-item>
<el-form-item label="密码" prop="password">
<el-input type="password" placeholder="输入用户密码" v-model="loginForm.password"></el-input>
</el-form-item>
<el-form-item class="tc">
<el-button type="primary" @click="doLogin">登 录</el-button>
<el-button>重 置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</template>
<script>
// import { userLogin } from '@/api/user'
import { mapActions } from 'vuex'
export default {
name: 'login-page',
data () {
return {
loginForm: {
username: 'admin',
password: 'admin'
},
rules: {
// trigger 可以支持两种类型: 字符串 / 字符串的数组
username: [
{ required: true, message: '用户名不能为空', trigger: 'blur' },
// 校验数据的长度范围
// 方法1
{ min: 5, max: 11, message: '长度在5到11个字符', trigger: 'blur' }
],
password: [
{ required: true, message: '密码不能为空', trigger: ['blur', 'change'] },
// 长度校验用法2
// 正则匹配:完全匹配精准匹配,模糊匹配(包含匹配)
{ pattern: /^\w{5,11}$/, message: '密码长度在5到11个字符', trigger: ['blur', 'change'] }
]
}
}
},
methods: {
...mapActions('user', ['loginAction']),
doLogin () {
// 在真正登录之前,手动校验一次
// 如何手动校验=>调用el-form组件实例的validate()
// 如何得到el-form组件实例 ref+$refs
// validate返回的是一个promise, 错误就会进catch
// .then校验成功
// .catch校验失败
// this.$refs.form.validate().then(() => {
// console.log('校验成功')
// // 调用接口,发请求,获取token
// // 跳转到管理台首页
// }).catch(err => {
// console.log(err, '校验失败')
// })
// 回调函数会返回一个布尔值 true为填写符合规范false反之
this.$refs.form.validate(async (isok) => {
console.log(isok)
// isok: false ==> 校验失败 ==> 提示用户
// isok true ==>校验成功 ==> 登录流程
// 登录流程是什么?
// 1. 发请求, 换取token
// 2. 跳转到首页 /dashboard
if (!isok) return
this.loginAction(this.loginForm)
// //const res = await userLogin(this.loginForm)
// // console.log(res)
// // console.log(res.data.token)
// //const { token } = res.data
// //console.log(token)
})
}
}
}
</script>
<style lang="scss" scoped>
.login-page {
min-height: 100vh;
background: url(@/assets/login-bg.svg) no-repeat center / cover;
display: flex;
align-items: center;
justify-content: space-around;
.el-card {
width: 420px;
// 深度作用选择器:从当前去修改子组件深层穿透样式less中是/deep/
::v-deep .el-card__header {
height: 80px;
background: rgba(114, 124, 245, 1);
text-align: center;
line-height: 40px;
color: #fff;
font-size: 18px;
}
}
.el-form {
padding: 0 20px;
}
.tc {
text-align: center;
}
}
</style>
封装的好处
//utils/storage.js
// 增删改查
// localstorage
// setTime
// setItem :增 改
// getItem :查
// removeItem:删
// 封装的好处
// 1. 可以实现模块化
// 2. 可维护
// 如果未来需要修改本地存储方案的话,直接改当前这个文件即可
export function setStorage (key, value) {
return localStorage.setItem(key, value)
}
export function getStorage (key) {
return localStorage.getItem(key)
}
export function delStorage (key) {
return localStorage.removeItem(key)
}
//store/index.js
import { userLogin } from '@/api/user'
import Vue from 'vue'
import Vuex from 'vuex'
import { setStorage, getStorage } from '@/utils/storage'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
user: {
// 开启命名空间
namespaced: true,
state: {
// token: '',
// 当页面初始化的时候,从本地存储中读回token,如果读出来是 undefined ,就用 空字符串 代替
token: getStorage('HM-TOKEN') || ''
},
mutations: {
setToken (state, token) {
state.token = token
// 将token持久化存储
setStorage('HM-TOKEN', token)
}
},
actions: {
async loginAction (context, payload) {
// 发请求,换token
// 提交mutations,更新token
const res = await userLogin(payload)
const token = res.data.token
// 设置token到store中
context.commit('setToken', token)
}
}
}
}
})
//store/modules/user.js
import { userLogin } from '@/api/user'
import { setStorage, getStorage } from '@/utils/storage'
export default {
// 开启命名空间
namespaced: true,
state: {
// token: '',
// 当页面初始化的时候,从本地存储中读回token,如果读出来是 undefined ,就用 空字符串 代替
token: getStorage('HM-TOKEN') || ''
},
mutations: {
setToken (state, token) {
state.token = token
// 将token持久化存储
setStorage('HM-TOKEN', token)
}
},
actions: {
async loginAction (context, payload) {
// 发请求,换token
// 提交mutations,更新token
const res = await userLogin(payload)
const token = res.data.token
// 设置token到store中
context.commit('setToken', token)
}
}
}
//store/index.js
import Vue from 'vue'
import Vuex from 'vuex'
import userStore from '@/store/modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
getters: {
},
mutations: {
},
actions: {
},
modules: {
user: userStore
}
})
这里登录操作是异步的,发请求需要等到拿回token结果以后,才能跳转到页面
//src/permission.js
// 专门用来处理路由权限拦截
// 路由权限拦截,本质上就是页面的访问拦截
// 再本质上,就是路由前置守卫
import router from './router'
import store from './store'
// to:去哪里
// from:从哪来
// next:是否放行
// 放行:next()
// 不放行: 引导到其他页面next(路由地址)
router.beforeEach((to, from, next) => {
// 已登录:全部放行
// 未登录:只能访问登录
// 登录页面:是一个白名单页面
// 需要获取用户的token,来判断用户是否已登录
const token = store.getters.token
// if (token) {
// // 说明已登录=>放行
// next()
// } else {
// if (to === '/login') {
// // 没有登录=>去登录页面=>放行
// next()
// } else {
// // 没有登录,要去非登录页面==>不能放行==>引导到登录页面
// next('/login')
// }
if (token || to.path === '/login') {
// 没有登录=>去登录页面=>放行
// 说明已登录=>放行,
return next()
}
// 没有登录,要去非登录页面==>不能放行==>引导到登录页面
next('/login')
})
// // 白名单,定义成登录
// const whiteList = ['/login']
// // 路由导航守卫
// router.beforeEach((to, from, next) => {
// // 1. 看有没有 token (vuex),如果有,直接放行
// const token = store.state.user.token
// if (token) {
// next()
// return
// }
// // 2. 看是否在 白名单,如果在,直接放行
// if (whiteList.includes(to.path)) {
// next()
// return
// }
// // 3. 其他情况,拦截到登录
// next('/login')
// })
//api/user.js
// 封装获取用户信息的接口
export function getUser () {
return request.get('/auth/currentUser')
}
//utils/request.js
// 如果有token就自动完成注入
if (store.getters.token) {
config.headers.Authorization = `Bearer ${store.getters.token}`
}