登录系统
- 用户初次登录,浏览器中未存用户信息(token),需向后端请求并保存至浏览器中
- 用户再次登录系统,向后端发请求会携带token在请求头中,并与后端Redis缓存的token比较,判断token是否还在有效期,若失效需要提示用户重新登录(响应拦截器实现),更新token信息
若依框架登录鉴权详解(动态路由)_若依鉴权-CSDN博客
前端登录鉴权——以若依Ruoyi前后端分离项目为例解读_若依鉴权-CSDN博客
前端基础知识:
axios请求封装,封装请求拦截器和响应拦截器
- 请求:请求头设置、设置防止重复提交(sessionStorage)
//1.headers中的content-type 默认的大多数情况是 (application/json),就是json序列化的格式
//2.为了判断是否为formdata格式,增加了一个变量为type
//如果type存在,而且是form的话,则代表是UrlSearchParams(application/x-www-form-urlencoded)的格式
// if (config.type && config.type === 'url-form') {
// config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
// //!!!不是所有浏览器都支持UrlSearchParams,可以用qs库编码数据
// if (config.data) {
// config.data = qs.stringify(config.data)
// }
// }
//3.FormData ('multipart/form-data'), axios会帮忙处理
//Axios 会将传入数据序列化,因此使用 Axios 提供的 API 可以无需手动处理 FormData
- 响应:根据响应状态码设置对应操作,例如401表示token失效->重新登录(同时清除浏览器Cookie设置的token\permission\role等信息
if (code === 401) {
if (!isRelogin.show) {
isRelogin.show = true;
MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => {
isRelogin.show = false;
store.dispatch('LogOut').then(() => {
location.href = process.env.VUE_APP_CONTEXT_PATH + "index";
})
}).catch(() => {
isRelogin.show = false;
});
}
//location.href 是一个通用的 Web API,适用于任何 Web 页面,但它在 Vue.js 应用中可能不是最优选择,因为它会重新加载整个页面并丢失当前页面的状态。
//Vue Router 的 push 方法是专门为 Vue.js 应用设计的,它提供了更灵活、更高效的导航方式,并支持 Vue Router 的所有高级功能。
页面分析
Vue中渲染函数_vue 渲染函数-CSDN博客
main.js:创建vm实例,实例中渲染函数h返回虚拟dom树,最后虚拟dom树挂载至真实dom(id为app:#app)上
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
App.vue,<router-viewer>标签,占位,用于展示路由对应的组件内容->Vue Router
<template>
<div id="app">
<router-view />
<theme-picker />
</div>
</template>
router/index.js:设置静态路由与配置动态路由
嵌套组件
嵌套路由:
嵌套路由配置定义在 Layout
组件内部,意味着当访问与这些子路由匹配的路径时,Layout
组件将作为父容器被渲染,而具体的子组件(如 index
对应的组件)则会在 Layout
组件AppMain内部的某个 <router-view>
中被渲染。
{
path: '',
component: Layout,
redirect: 'index',
children: [
{
path: 'index',
component: () => import('@/views/index'),
name: 'Index',
meta: { title: '首页', icon: 'dashboard', affix: true },
}
]
},
//Layout(src/layout/index)->AppMain.vue(src/layout/components/AppMain.vue)->动态路由[例() => import('@/views/index')]
//level1 Layout(src/layout/index)
<template>
<div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
<sidebar v-if="!sidebar.hide" class="sidebar-container"/>
<div :class="{hasTagsView:needTagsView,sidebarHide:sidebar.hide}" class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar/>
<tags-view v-if="needTagsView"/>
</div>
<app-main/>
<right-panel>
<settings/>
</right-panel>
</div>
</div>
</template>
//level2 AppMain.vue(src/layout/components/AppMain.vue)
<template>
<section class="app-main">
<transition name="fade-transform" mode="out-in">
<keep-alive :include="cachedViews">
//level3 动态路由
<router-view v-if="!$route.meta.link" :key="key" />
</keep-alive>
</transition>
<iframe-toggle />
</section>
</template>
页面布局
Layout/index
<template>
<div :class="classObj" class="app-wrapper" :style="{'--current-color': theme}">
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
<sidebar v-if="!sidebar.hide" class="sidebar-container"/>
<div :class="{hasTagsView:needTagsView,sidebarHide:sidebar.hide}" class="main-container">
<div :class="{'fixed-header':fixedHeader}">
<navbar/>
<tags-view v-if="needTagsView"/>
</div>
<app-main/>
<right-panel>
<settings/>
</right-panel>
</div>
</div>
</template>
设备类型监测,根据设备类型设置右侧SideBar是否显示(v-if)
封装ResizeHander.js mixin混入
核心
- window.addEventListener('resize', this.$_resizeHandler)
- bootstrap's responsive design
- Vuex
<div v-if="device==='mobile'&&sidebar.opened" class="drawer-bg" @click="handleClickOutside"/>
import store from '@/store'
const { body } = document
const WIDTH = 992 // refer to Bootstrap's responsive design
export default {
watch: {
$route(route) {
if (this.device === 'mobile' && this.sidebar.opened) {
store.dispatch('app/closeSideBar', { withoutAnimation: false })
}
}
},
beforeMount() {
window.addEventListener('resize', this.$_resizeHandler)
},
beforeDestroy() {
window.removeEventListener('resize', this.$_resizeHandler)
},
mounted() {
const isMobile = this.$_isMobile()
if (isMobile) {
store.dispatch('app/toggleDevice', 'mobile')
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
},
methods: {
// use $_ for mixins properties
// https://vuejs.org/v2/style-guide/index.html#Private-property-names-essential
$_isMobile() {
const rect = body.getBoundingClientRect()
return rect.width - 1 < WIDTH
},
$_resizeHandler() {
if (!document.hidden) {
const isMobile = this.$_isMobile()
store.dispatch('app/toggleDevice', isMobile ? 'mobile' : 'desktop')
if (isMobile) {
store.dispatch('app/closeSideBar', { withoutAnimation: true })
}
}
}
}
}
更改页面布局配置:主题颜色、布局、大小等(vuex+cookie、screenfull库)
1.封装与注册插件(包括$cache 对象、$tab对象、$modal对象)
//plugins/index.js
tab from './tab'
import auth from './auth'
import cache from './cache'
import modal from './modal'
import download from './download'
export default {
install(Vue) {
// 页签操作
Vue.prototype.$tab = tab
// 认证对象
Vue.prototype.$auth = auth
// 缓存对象
Vue.prototype.$cache = cache
// 模态框对象
Vue.prototype.$modal = modal
// 下载文件
Vue.prototype.$download = download
}
}
//plugins/cache.js->sessionStorage/localStorage
......
export default {
/**
* 会话级缓存
*/
session: sessionCache,
/**
* 本地缓存
*/
local: localCache
}
2.SCSS变量应用于Javascript中
// the :export directive is the magic sauce for webpack
// https://www.bluematador.com/blog/how-to-share-variables-between-js-and-sass
:export {
menuColor: $base-menu-color;
menuLightColor: $base-menu-light-color;
menuColorActive: $base-menu-color-active;
menuBackground: $base-menu-background;
menuLightBackground: $base-menu-light-background;
subMenuBackground: $base-sub-menu-background;
subMenuHover: $base-sub-menu-hover;
sideBarWidth: $base-sidebar-width;
logoTitleColor: $base-logo-title-color;
logoLightTitleColor: $base-logo-light-title-color
}
//Vue组件中
:background-color="settings.sideTheme === 'theme-dark' ? variables.menuBackground : variables.menuLightBackground"
3.面包屑中首页/目录1/目录1.1 首页重定向功能
$route(newValue,oldValue)监测路由的变化
//template
<el-breadcrumb-item v-for="(item,index) in levelList" :key="item.path">
<span v-if="item.redirect === 'noRedirect' || index == levelList.length - 1" class="no-redirect">{{ item.meta.title }}</span>
<a v-else @click.prevent="handleLink(item)">{{ item.meta.title }}</a>
</el-breadcrumb-item>
//Javascript
getBreadcrumb() {
// only show routes with meta.title
let matched = this.$route.matched.filter(item => item.meta && item.meta.title)
const first = matched[0]
if (!this.isDashboard(first)) {
//在前面附加一个首页,附加后的“首页/系统管理/部门管理"
matched = [{ path: '/index', meta: { title: '首页' }}].concat(matched)
}
this.levelList = matched.filter(item => item.meta && item.meta.title && item.meta.breadcrumb !== false)
},
handleLink(item) {
const { redirect, path } = item
if (redirect) {
this.$router.push(redirect)
return
}
this.$router.push(path)
}
}
4.flex布局与流式布局
CSS常见适配布局方式_css百分比布局-CSDN博客