第五节

1、路由导航守卫-判断用户是否已经登录

  1. // 判断是否登录
  2. router.beforeEach((to) => {
  3. if (to.path !== '/login') {
  4. const token = localCache.getCache('token')
  5. if (!token) {
  6. return '/login'
  7. }
  8. }
  9. })

2、用户刷新页面后Pinia数据重载(第五节 后期)

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. import router from './router'
  4. import { createPinia } from 'pinia'
  5. import { useLoginStore } from './store/login/login'
  6. import 'normalize.css'
  7. import './assets/css/index.less'
  8. const app = createApp(App)
  9. app.use(router)
  10. app.use(createPinia())
  11. // 每次刷新页面都加载本地信息,将本地信息同步到pinia
  12. const loginStore = useLoginStore()
  13. loginStore.loadLocalLogin()
  14. app.mount('#app')
  1. import { defineStore } from 'pinia'
  2. import localCache from '@/utils/cache'
  3. import router from '@/router'
  4. export const useLoginStore = defineStore('login', {
  5. state: () => ({
  6. token: '',
  7. userInfo: {},
  8. userMenus: []
  9. }),
  10. getters: {},
  11. actions: {
  12. // 这个方法要在main.ts中调用
  13. loadLocalLogin() {
  14. const token = localCache.getCache('token')
  15. if (token) {
  16. this.token = token
  17. }
  18. const userInfo = localCache.getCache('userInfo')
  19. if (userInfo) {
  20. this.userInfo = userInfo
  21. }
  22. const userMenus = localCache.getCache('userMenus')
  23. if (userMenus) {
  24. this.userMenus = userMenus
  25. }
  26. }
  27. }
  28. })

第六节

1、main页面目录结构设计

image.pngindex.ts是专门做导出的

image.png

2、在模板中使用目录别名

  1. <template>
  2. <img src="~@/assets/logo.png" alt="logo" />
  3. </template>

3、菜单接口类型

  1. {
  2. "code": 0,
  3. "data": {
  4. "list": [
  5. {
  6. "id": 1,
  7. "name": "系统管理",
  8. "type": 1, // type=1:可以展开的菜单
  9. "url": "/main/system",
  10. "icon": "el-icon-setting",
  11. "sort": 2,
  12. "createAt": "2021-01-02T10:08:14.000Z",
  13. "updateAt": "2021-08-20T07:00:08.000Z",
  14. "children": [
  15. {
  16. "id": 2,
  17. "url": "/main/system/user",
  18. "name": "用户管理",
  19. "sort": 100,
  20. "type": 2, // type=2 页面选项
  21. "children": [
  22. {
  23. "id": 5,
  24. "url": null,
  25. "name": "创建用户",
  26. "sort": null,
  27. "type": 3, // type=3 按钮权限
  28. "createAt": "2021-01-03 13:41:01.000000",
  29. "parentId": 2,
  30. "updateAt": "2021-08-19 16:10:06.000000",
  31. "permission": "system:users:create"
  32. }
  33. ],
  34. "createAt": "2021-01-02 18:08:47.000000",
  35. "parentId": 1,
  36. "updateAt": "2021-08-19 15:52:01.000000"
  37. }
  38. ]
  39. },
  40. {
  41. "id": 38,
  42. "name": "系统总览",
  43. "type": 1,
  44. "url": "/main/analysis",
  45. "icon": "el-icon-monitor",
  46. "sort": 1,
  47. "createAt": "2021-04-19T14:11:02.000Z",
  48. "updateAt": "2021-08-20T06:59:55.000Z",
  49. "children": [
  50. {
  51. "id": 39,
  52. "url": "/main/analysis/overview",
  53. "name": "核心技术",
  54. "sort": 106,
  55. "type": 2,
  56. "children": null,
  57. "createAt": "2021-01-02 18:09:11.000000",
  58. "parentId": 38,
  59. "updateAt": "2021-08-19 15:54:41.000000"
  60. }
  61. ]
  62. }
  63. ]
  64. }
  65. }

对应的Element 展示模板

  1. <template>
  2. <div class="nav-menu">
  3. <div class="logo">
  4. <img class="img" src="~@/assets/img/logo.svg" alt="logo" />
  5. <span v-if="!collapse" class="title">Vue3+TS</span>
  6. </div>
  7. <!-- ======================菜单================= -->
  8. <el-menu
  9. default-active="2"
  10. class="el-menu-vertical"
  11. :collapse="collapse"
  12. background-color="#0c2135"
  13. text-color="#b7bdc3"
  14. active-text-color="#0a60bd"
  15. >
  16. <template v-for="item in userMenus" :key="item.id">
  17. <!-- 一级菜单 -->
  18. <template v-if="item.type === 1">
  19. <!-- 一级菜单的可以展开的标题 -->
  20. <el-submenu :index="item.id + ''">
  21. <!-- 一级菜单信息 -->
  22. <template #title>
  23. <i v-if="item.icon" :class="item.icon"></i>
  24. <span>{{ item.name }}</span>
  25. </template>
  26. <!-- 遍历里面的item -->
  27. <template v-for="subitem in item.children" :key="subitem.id">
  28. <el-menu-item :index="subitem.id + ''">
  29. <i v-if="subitem.icon" :class="subitem.icon"></i>
  30. <span>{{ subitem.name }}</span>
  31. </el-menu-item>
  32. </template>
  33. </el-submenu>
  34. </template>
  35. <!-- 二级菜单 -->
  36. <template v-else-if="item.type === 2">
  37. <el-menu-item :index="item.id + ''">
  38. <i v-if="item.icon" :class="item.icon"></i>
  39. <span>{{ item.name }}</span>
  40. </el-menu-item>
  41. </template>
  42. </template>
  43. </el-menu>
  44. </div>
  45. </template>

4、权限管理的原理

image.png

动态路由的几种方案

第一种
将所有的url映射都写出来,看看当前用户有什么菜单,就显示什么
缺点:如果用户直接输入url,会有风险!
第二种
动态路由的第二种方案.png
缺点,角色是枚举出来的,映射是写死的,如果新添加角色,就得修改前端代码!!
第三种
动态路由的第三种方案.jpg
这里就需要后端的配合了!后端需要在数据库中为每个菜单存一个url!!!

第七节

1、高级组件封装

  1. <script lang="ts">
  2. import { defineComponent, PropType } from 'vue'
  3. import { IFormItem } from '../types'
  4. export default defineComponent({
  5. props: {
  6. formItems: {
  7. type: Array as PropType<IFormItem[]>,
  8. default: () => [] // 默认值为数组或者对象时,要用箭头函数,和this有关系
  9. },
  10. labelWidth: {
  11. type: String,
  12. default: '100px'
  13. },
  14. itemStyle: {
  15. type: Object,
  16. default: () => ({ padding: '10px 40px' })
  17. },
  18. colLayout: {
  19. type: Object,
  20. default: () => ({
  21. xl: 6, // >1920px 4个
  22. lg: 8,
  23. md: 12,
  24. sm: 24,
  25. xs: 24
  26. })
  27. }
  28. },
  29. setup() {
  30. return {}
  31. }
  32. })
  33. </script>

第八节

1、一刷新就NotFound

  1. app.use(router)
  2. // 每次刷新页面都加载本地信息,将本地信息同步到pinia
  3. const loginStore = useLoginStore()
  4. loginStore.loadLocalLogin()

换一下顺序即可

  1. const loginStore = useLoginStore()
  2. loginStore.loadLocalLogin()
  3. app.use(router)

2、路径映射出菜单

先写工具函数

  1. // 这波递归很溜
  2. export function pathMapToMenu(userMenus: any[], currentPath: string): any {
  3. for (const menu of userMenus) {
  4. if (menu.type === 1) {
  5. const findMenu = pathMapToMenu(menu.children ?? [], currentPath) // 这里接住!
  6. if (findMenu) {
  7. return findMenu
  8. }
  9. } else if (menu.type===2 && menu.url === currentPath) {
  10. return menu // 这里返回
  11. }
  12. }
  13. }

然后使用

  1. <template>
  2. <div class="nav-menu">
  3. <div class="logo">
  4. <img class="img" src="~@/assets/img/logo.svg" alt="logo" />
  5. <span v-if="!collapse" class="title">Vue3+TS</span>
  6. </div>
  7. <el-menu :default-active="defaultValue" class="el-menu-vertical" :collapse="collapse" background-color="#0c2135"
  8. text-color="#b7bdc3" active-text-color="#0a60bd">
  9. <!--省略。。。-->
  10. </el-menu>
  11. </div>
  12. </template>
  13. <script lang="ts">
  14. import { defineComponent, computed, ref } from 'vue'
  15. import { useLoginStore } from '@/store/login/login'
  16. import { useRouter, useRoute } from 'vue-router'
  17. import { pathMapToMenu } from '@/utils/map-menus'
  18. // vuex - typescript => pinia
  19. export default defineComponent({
  20. props: {
  21. collapse: {
  22. type: Boolean,
  23. default: false
  24. }
  25. },
  26. setup() {
  27. // store
  28. const store = useLoginStore()
  29. const userMenus = computed(() => store.userMenus)
  30. //router (学习一下是如何使用route和router的)
  31. const router = useRouter()
  32. const route = useRoute()
  33. const currentPath = route.path // 拿到当前路径
  34. // data
  35. const menu = pathMapToMenu(userMenus.value, currentPath) // 因为computed返回的是ref,所以要用其value
  36. const defaultValue = ref(menu.id + '') // 因为组件只接受字符串,所以这里加一个空字符串
  37. // 事件处理
  38. const handleMenuItemClick = (item: any) => {
  39. router.push({
  40. path: item.url ?? '/not-found'
  41. })
  42. }
  43. return {
  44. userMenus,
  45. defaultValue,
  46. handleMenuItemClick
  47. }
  48. }
  49. })
  50. </script>

尝试一下,问题解决,但是现在又有了另一个问题:
当尝试调到首页时,啥也不展示了,这是为什么呢?
很简单,因为main不对应任何一个菜单,上面的//data部分,获得的menu是空的,自然没有menu.id,这地方就报错了!
理想情况是:当用户访问首页时,我们定位到第一个菜单上:
只需在src\utils\map-menus.ts中定义一个firstMenu变量,在动态加载路由将其记录下来,然后再回到src\router\index.ts中,加一个全局的路由导航守卫就OK了。

  1. router.beforeEach((to) => {
  2. if (to.path === '/main') {
  3. return firstMenu.url // 注意,是返回路由的url字符串,而非给to.path赋值!
  4. }
  5. })