登录系统
- 用户初次登录,浏览器中未存用户信息(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 designexport 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>
//JavascriptgetBreadcrumb() {// only show routes with meta.titlelet 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 } = itemif (redirect) {this.$router.push(redirect)return}this.$router.push(path)}}
4.flex布局与流式布局
CSS常见适配布局方式_css百分比布局-CSDN博客