# 主页模块
目标:实现主页基本布局效果主页的布局组件位置**
src/layout**

# 主页布局-左侧菜单
目标:实现左侧菜单效果
- 左侧导航样式处理
styles/siderbar.scss
// 设置背景渐变色
.sidebar-container {
background: -webkit-linear-gradient(bottom, #3d6df8, #5b8cff);
}
// 设置左侧导航背景图片
.scrollbar-wrapper {
background: url('~@/assets/common/leftnavBg.png') no-repeat 0 100%;
}
// 设置菜单选中颜色
.el-menu {
border: none;
height: 100%;
width: 100% !important;
a{
li{
.svg-icon{
color: #fff;
font-size: 18px;
vertical-align: middle;
.icon{
color:#fff;
}
}
span{
color: #fff;
}
&:hover{
.svg-icon{
color: #43a7fe
}
span{
color: #43a7fe;
}
}
}
}
}
因为我们后期没有二级菜单,所以这里暂时不对二级菜单的样式进行控制。
注意:variables.scss文件的变量要进行替换
# 主页布局-左侧图标
需求:定制SideBar菜单的Logo:
@/src/layout/components/Logo.vue
- 左侧logo图片显示效果调整
<div class="sidebar-logo-container" :class="{'collapse':collapse}">
<transition name="sidebarLogoFade">
<router-link key="collapse" class="sidebar-logo-link" to="/">
<img src="@/assets/common/logo.png" class="sidebar-logo ">
</router-link>
</transition>
</div>
- 设置大图和小图的样式
// 大图样式
& .sidebar-logo {
width: 140px;
vertical-align: middle;
margin-right: 12px;
}
// 小图样式
&.collapse {
.sidebar-logo {
margin-right: 0px;
width: 50px;
height: 24px;
}
}
总结:&和类名之间的空格问题
- 如果有空格,就是父子关系
- 如果没有空格就是兄弟关系(并列关系)
# 主页布局-头部导航
**
目标**设置头部内容的布局和样式
- 头部组件效果
layout/components/Navbar.vue
<div class="app-breadcrumb">
江苏传智播客教育科技股份有限公司
<span class="breadBtn">体验版</span>
</div>
<!-- <breadcrumb class="breadcrumb-container" /> -->
- 布局样式
.navbar {
background-image: -webkit-linear-gradient(left, #3d6df8, #5b8cff);
.app-breadcrumb {
display: inline-block;
font-size: 18px;
line-height: 50px;
margin-left: 10px;
color: #ffffff;
cursor: text;
.breadBtn {
background: #84a9fe;
font-size: 14px;
padding: 0 10px;
display: inline-block;
height: 30px;
line-height: 30px;
border-radius: 10px;
margin-left: 15px;
}
}
}
- 汉堡组件图标颜色
src/components/Hamburger/index.vue
<svg
:class="{'is-active':isActive}"
class="hamburger"
viewBox="0 0 1024 1024"
xmlns="http://www.w3.org/2000/svg"
width="64"
height="64"
fill="#fff"
>
注意这里的图标我们使用了svg,设置颜色需要使用svg标签的**fill属性**
- 右侧下拉菜单设置
<div class="right-menu">
<el-dropdown class="avatar-container" trigger="click">
<div class="avatar-wrapper">
<img src="@/assets/common/bigUserHeader.png" class="user-avatar">
<span class="name">管理员</span>
<i class="el-icon-caret-bottom" style="color:#fff" />
</div>
<el-dropdown-menu slot="dropdown" class="user-dropdown">
<router-link to="/">
<el-dropdown-item>
首页
</el-dropdown-item>
</router-link>
<a target="_blank" href="https://xxx.com">
<el-dropdown-item>项目地址</el-dropdown-item>
</a>
<el-dropdown-item divided @click.native="logout">
<span style="display:block;">退出登录</span>
</el-dropdown-item>
</el-dropdown-menu>
</el-dropdown>
</div>
- 头像和下拉菜单样式
.avatar-wrapper {
position: relative;
.user-avatar {
cursor: pointer;
width: 30px;
height: 30px;
border-radius: 15px;
vertical-align: middle;
}
.name {
cursor: pointer;
color: #fff;
vertical-align: middle;
margin-left:5px;
}
.user-dropdown {
color: #fff;
}
.el-icon-caret-bottom {
cursor: pointer;
position: absolute;
right: -20px;
top: 20px;
font-size: 12px;
}
}
总结:汉堡菜单;面包屑导航;右侧下拉菜单
注意:svg的样式控制使用fill属性填充颜色
# 获取用户信息
目标封装获取用户资料的资料信息上小节中,我们完成了头部菜单的基本布局,但是头像和名称没有,需要通过接口调用的方式获取当前用户的资料信息
- 获取用户资料接口
src/api/user.js
export function reqGetUserInfo() {
return request({
url: '/sys/profile',
method: 'post'
})
}
这个接口, 需要配置 headers 请求头, 配置 token, 而我们在请求任何带安全权限的接口时都需要**
令牌(token)** ,每次在接口中携带**令牌(token)**很麻烦,所以我们可以在axios拦截器中统一添加token。src/utils/request.js
// 请求拦截器
instance.interceptors.request.use(function(config) {
// 在发送请求之前做些什么
if (store.getters.token) {
// 如果token存在 注入token
config.headers = {
Authorization: `Bearer ${store.getters.token}`
}
}
return config
}, function(error) {
// 对请求错误做些什么
return Promise.reject(error)
})
- 前端解决跨域问题
// 在vue.config.js文件中配置代理
devServer: {
port: port,
// 自动打开浏览器
open: true,
overlay: {
warnings: false,
errors: true
},
proxy: {
// 所有的请求路径以api开始的地址都会被代理
// 发送的请求 http://localhost:9528/api/login
// 代理的目标 http://ihrm-java.itheima.net/api/login
'^/api': {
// 代理的目标地址
target: 'http://ihrm-java.itheima.net'
}
}
// before: require('./mock/mock-server.js')
},
// 实际发送的请求基准路径调整为本地地址 .env.development
VUE_APP_BASE_API = 'http://localhost:9528/api/'

# 回顾
- 登录
- 基本布局
- 表单验证
- 基于action方式调用接口实现登录
- 封装本地存储的操作
- 处理响应拦截器
- 导航守卫控制
- 控制密码的显示与否:$nextTick
- 环境变量的用法:控制开发和生产环境采用不同的资源
- 主页
- 基本布局
- 左侧导航
- 顶部导航
- 调用接口获取用户信息
- 配置前端跨域的代理:Vue/cli提供的配置
- 配置请求拦截器,统一添加请求头token
- 理解跨域配置流程(这种方案仅仅用于开发阶段)
- 基本布局
# vuex存储用户资料
目标: 在用户的vuex模块中封装获取用户资料的action,并存储相关状态到vuex中用户状态会在后续的开发中,频繁用到,所以我们将用户状态同样的封装到action中
- 封装获取用户资料action
actionsrc/store/modules/user.js
import { reqLogin, getUserInfo } from '@/api/user'
actions: {
// 获取用户数据
async getUserInfo (context) {
const ret = await reqGetUserInfo()
context.commit('setUserInfo', ret.data)
}
}
- navBar 组件中调用
import { mapActions } from 'vuex'
created() {
this.getUserInfo()
},
methods: {
...mapActions('user', ['getUserInfo']),
}
- 提交mutation, 将用户信息存储到 vuex 中
const state = {
token: getToken(), // 优先从cookie中读取
userInfo: {}
}
// mutations重要的原则: 只能是同步的
const mutations = {
// 设置token
...,
setUserInfo(state, newUserInfo) {
state.userInfo = newUserInfo
}
}
- 页面中使用
const getters = {
sidebar: state => state.app.sidebar,
device: state => state.app.device,
token: state => state.user.token,
name: state => state.user.userInfo.username // 建立用户名称的映射
}
export default getters
computed: {
...mapGetters([
'sidebar',
'avatar',
'name'
])
},
<span class="name">{{name}}</span>
总结:
- 通过action调用接口获取用户信息
- 把获取的数据更新到state里面
- Layout组件中触发action
- 通过getters解析用户信息中的name
- 模板中显示用户信息
# 获取用户头像
目标:获取用户头像信息我们发现头像并不在接口的返回体中(接口原因),我们可以通过另一个接口来获取头像,并把头像合并到当前的资料中
- 封装获取用户信息接口
src/api/user.js
// 获取用户头像信息
export function reqGetUserDetailById(id) {
return request({
url: `/sys/user/${id}`
})
}
import { reqLogin, reqGetUserInfo, reqGetUserDetailById } from '@/api/user'
// 获取用户数据
async getUserInfo (context) {
// 获取用户的基本信息
const baseInfo = await reqGetUserInfo()
// 根据用户id获取详细信息
const detailInfo = await reqGetUserDetailById(baseInfo.data.userId)
// 更新用户所有信息
context.commit('setUserInfo', {
...baseInfo.data,
...detailInfo.data
})
}
- 为了页面中更好地获取头像,同样可以把头像放于getters中
avatar: state => state.user.userInfo.staffPhoto // 建立用户头像的映射
- 展示头像**
layout/components/Navbar.vue**
computed: {
...mapGetters([
'sidebar',
'avatar',
'name'
])
},
<img :src="avatar" class="user-avatar">
<span class="name">{{ name }}</span>
总结:基于Action获取用户的详细信息
基于延展运算符合并对象的写法
上一个接口调用的结果作为下一个接口调用的参数(注意第一次调用的await是必要的)
# 处理头像失效问题
目标:处理图片加载失败时的默认显示效果
- 注册自定义指令基本用法
v-model.lazy='uname'- lazy指令修饰符的作用:把v-model的默认事件input修改为change事件
- change事件的触发条件是输入框失去焦点(内容需要有变化)
- input事件的触发条件是文本发生变化
- blur失去焦点触发
Vue.directive('指令名称', {
// 会在当前指令作用的dom元素 插入之后执行
// el 指令所在dom元素
// bindings 里面是指令的参数信息对象
inserted(el, bindings) {
}
})
- 当图片有地址 但是地址没有加载成功的时候 会报错 会触发图片的一个事件 => onerror
/*
封装Vue插件
*/
export default {
install (Vue, options) {
// 扩展一个自定义指令,处理图片加载失败的情况
// <img v-imgerror='default.png' src="a.png" alt=""/>
Vue.directive('imgerror', {
// bindings包含指令相关的参数信息
inserted (el, bindings) {
console.dir(bindings)
// 如何知道img标签图片加载失败了?
el.onerror = () => {
// 加载失败后触发该函数
el.src = bindings.value || options.defaultImg
}
}
})
}
}
// main.js
// 导入插件
import MyPlugins from '@/utils/plugins.js'
// 配置插件
Vue.use(MyPlugins, { defaultImg: 'default.png' })
- 使用指令, 这里图片如果是用本地图片, 需要导入, 如果是完整地址的网图, 直接赋值即可
<img v-imgerror="defaultImg" :src="avatar" class="user-avatar">
import Img from '@/assets/common/head.jpg'
data() {
return {
defaultImg: Img
}
},
// 或者直接完整地址的网图赋值
data() {
return {
defaultImg: 'https://dss1.bdstatic.com/70cFuXSh_Q1YnxGkpoWK1HF6hhy/it/u=2344451607,2404623174&fm=111&gp=0.jpg'
}
},
总结:
- 自定义指令的基本规则
- 插件基本使用规则:先定义,再导入并配置
- 配置插件时,可以传递options选项
- 扩展图片加载的自定义指令
- 使用自定义指令
# 退出功能
目标:实现用户的退出操作

- 退出action
src/store/modules/user.js
import { getToken, setToken, removeToken } from '@/utils/auth'
// mutations重要的原则: 只能是同步的
mutations: {
// 删除缓存的token
removeToken (state) {
state.token = ''
removeToken()
}
},
- 头部菜单调用action
src/layout/components/Navbar.vue
methods: {
...mapMutations('user', ['setUserInfo', 'removeToken']),
async logout() {
// await this.$store.dispatch('user/logout')
// this.$router.push(`/login?redirect=${this.$route.fullPath}`)
// 删除用户的token和信息,跳转到登录页面
this.removeToken()
this.setUserInfo({})
this.$router.push('/login')
}
}
总结:
- 清除token
- 清除用户信息
- 跳转到登录页面
# 处理token失效问题
目标: 实现token失效的处理token超时的错误码是**
10002**
- 拦截器处理token失效
src/utils/request.js
// 响应拦截器
instance.interceptors.response.use(function(response) {
// 对响应数据做点什么
return response.data
}, function(error) {
// 判断token是否失效
if (error.response.status === 401 && error.response.data.code === 10002) {
// token已经失效,删除用户信息,跳转到登录页面
store.commit('user/removeToken')
store.commit('user/setUserInfo', {})
router.push('/login')
} else {
// 不是401,也不是200,那么说明是其他错误,直接进行提示
Message.error(error.response.message)
}
return Promise.reject(error)
})
总结:
- 判断token过期的情况
- 判断服务器失败的其他情况
- 在组件中触发mutation没有添加user前缀,因为映射时已经添加
- 在非组件环境触发mutation需要添加模块的前缀