安康同行难点和亮点

难点

菜单权限实现

菜单权限就是在登录请求中,获取到权限数据,根据相应的数据,展示菜单信息,点击菜单后,才能看到相关的页面,理解成将页面与理由进行解耦。
实现菜单权限主要有两种方式
方案一:
菜单与路由分离,菜单由后端返回,前端定义路由信息,路由信息中必须有name字段,后端返回的菜单信息中也必须有name字段,根据两者的name字段做唯一性校验。
实现方法:成功登录之后把后端菜单数据持久化存储,当进行菜单切换时,在全局路由守卫里面进行判断,看要跳转到的路由name是否存在于后端菜单数据里面,如果存在,就允许跳转,如果不存在,就跳转到自定义403页面。
缺点:菜单需要与路由做一一对应,前端添加了新功能,需要通过菜单管理功能添加新的菜单,如果菜单配置的不对会导致应用不能正常使用。
全局路由守卫里,每次路由跳转都要做判断

方案二
菜单和路由都由后端返回,前端统一定义路由组件,通过addRoutes进行动态挂载,将component字段换为真正的组件。

这个项目采用的方案二。

实现方法:
1.登录成功后,根据用户信息调用菜单权限接口获取菜单数据,把菜单数据传到pinia持久化存储定义的action中进行处理

menuPermissions().then(({data})=>{
            menuStore.dynamicMenu(data.data)
            }

2.在action中,要配置好route的component,进行页面渲染

async dynamicMenu(payload) {
            //通过glob导入文件
            const modules = import.meta.glob('../views/**/**/*.vue')
            function routerSet(router) {
                //遍历数据
                router.forEach(route => {
                    if (!route.children) {
                        //获得url
                        const url = `../views${route.meta.path}/index.vue`
                        //通过url匹配包
                        route.component = modules[url]

                    } else {
                        //递归
                        routerSet(route.children)
                    }
                });
            }
            routerSet(payload)
            //拿到完整的路由数据
            this.routerList = payload
            // console.log(this.routerList)
        },

其中,import.meta.glob是 ES 模块规范中的一个特性,接受俩参数,第一个参数是必须的,是一个字符串,用于指定模块路径的匹配模式。第二个参数是可选的,是一个对象,可用于配置模块导入的一些行为。
import.meta.glob返回对象形式,例如:

{
  "../views/home.vue": () => import("../views/home.vue")
}

最后把路由数据进行持久化处理
3.动态导入

toRaw(routerList.value).forEach(item => {
              router.addRoute('main',item)
            });

动态导入到main下
注:通过toRaw获取原始对象,可以确保传递给router.addRoute的是最原始、最纯净的数据,避免因为响应式包装而可能带来的一些数据结构或行为上的差异,提高性能。

问题:刷新页面菜单消失
原因:因为菜单数据是登录之后才获取到的, 获取菜单数据之后,就存放在pinia中 ⼀旦刷新界⾯, pinia中的数据会重新初始化
解决:需要将权限数据存储在localStorage中,数据和pinia中的一致,当刷新完成后,数据从localStorage中获取

//刷新后的动态路由添加
const localDate =  localStorage.getItem('ak_gl')
const menuStore = useMenuStore() 
if(localDate){
  menuStore.dynamicMenu(JSON.parse(localDate).routerList)
  menuStore.routerList.forEach(item => {
    router.addRoute('main',item)
  });
}

总结
菜单权限的难点在于如何处理后端数据,正确的设置好route的component,这是最关键也是最难的一步,在动态导入路由的时候怎么解决不必要的性能开销。

用jsonp解决跨域

通常用配置代理的方法解决跨域,但是在这个项目中,用到了某某地图的接口产生跨域,所以用jsonp的方式进行解决

实现方法:
1.在main.js中全局配置

//先导入vue-jsonp
import { jsonp } from 'vue-jsonp';

//进行全局挂载
app.config.globalProperties.$jsonp= jsonp 

2.在页面使用

import { getCurrentInstance} from 'vue';
//获取到实例
const { proxy  } = getCurrentInstance();
//直接使用
  proxy.$jsonp(url, data).then((res) => {}

难点在于如何把jsonp配置到全局之中,如何调用

顶部导航栏的删除

项目中有这样一个功能,当点击侧边栏的菜单时,会把点击的菜单名称添加到顶部导航栏,当叉掉当前所选菜单或者其他菜单时,根据逻辑操作顶部导航栏菜单的变化
增加菜单:
所有的激活菜单都要进行pinia状态管理
所以,我们要先在action中定义一个添加菜单的方法

//添加顶部菜单
//如果当前selectMenu内存在被点击的菜单,就不用添加,否则就添加
        addMenu(payload) {
            if (!this.selectMenu.some(item => item.path === payload.path)) {
                this.selectMenu.push(payload);
            }
        }

在侧边栏页面中调用addMenu方法

const handelClick =(item,active)=>{
  menuStore.addMenu(item.meta)
  }

删除菜单

点击查号会关闭页面
在这里插入图片描述
如果删除的不是当前所选的页面,那么直接在selectMenu中删除
如果删除的是所选的页面,那么,后续该怎样进行页面的切换,这是一个难点

实现方法:

//点击关闭tag
  const closeTab =(item,index)=>{
    menuStore.closeMenu(item)
    //删除非当前页,在store中直接删除
    if(route.path !== item.path){
      return
    }
    //获取菜单数据
    const selectMenuDate=selectMenu.value
    //删除的选中项是最后一项
    if(index === selectMenuDate.length){ 
    //可能会有人会说为什么和length相比,而不是length-1
    //因为index是从0开始的,刚开始的时候我也和lengh-1相比的,发现没有效果,然后我又检查前面的代码
    //发现menuStore.closeMenu(item)已经先在selectMenuDate删除了
    //所以这里进行判断的时候,selectMenuDate是已经删除过的

      //如果tag只有一个元素,即length已经变成0了
      if(!selectMenuDate.length){
      //跳到默认页面
        router.push('/')
      }else{
        //往前移动
        router.push({
          path:selectMenuDate[index-1].path
          //当前位置变为原来删除选项的index,要往前移动,index-1即可
        })

      }
    } else {
      //如果选中项位于中间位置,往后移动
      router.push({
        path:selectMenuDate[index].path
        //同样道理,index已经变成了原来选项所在的位置
        //删除后后面的选项往前移动,占据当前的index
      })
    }
  }

侧边栏菜单与顶部菜单高亮一致

在测试阶段中,发现侧边栏菜单与顶部导航菜单高亮不一致,在菜单删除,添加,切换的时候,侧边栏菜单高亮纹丝不动,经过反复测试,发现当顶部导航菜单转换的时候,active没有更新,但是后端里并没有关于active更新的操作,这时候就需要前端开发人员自行完成

实现方法:
1.侧边栏的高亮效果是通过<el-menu中的:default-active实现的

default-active="menuActive"

所以要进行状态管理,在pinia的state中定义menuActive
2.实现更新操作
先在action中定义一个更新menuActive的方法

 //更新active
        updateMenuActive(payload) {
            this.menuActive = payload;
        }

在顶部栏操作页面

页面较少的情况下,我们可以这样操作,通过对象键值对的方法获取到状态码,即路由的index

const getActive = (path)=>{
    const obj = {
      '/dashboard':'1-1',
      '/auth/admin':'1-2-1',
      '/auth/group':'1-2-2',
      '/vppz/staff':'1-3-1',
      '/vppz/order':'1-3-2'
    }
    return obj[path]
  }
// 在组件setup函数中使用onBeforeRouteUpdate导航守卫
    onBeforeRouteUpdate((to, from) => {
      //获取当前的路由path,执行store里面的更新active和menudate操作
      menuStore.updateMenuActive(getActive(to.meta.path))
    });

虽然看起来并不怎么难,但要想到用什么方式进行更新

亮点

二次封装local

二次封装local,提高安全性
因为这个项目有pc端和移动端两种,为了避免移动端的localStorage和pc端的localStorage冲突,对pc端中的localStorage二次封装

//对localstorge实现二次封装,提高安全性
const namespace = 'information'

export default {
    setItem(key, val) {
        //调用 getStorage 方法获取当前存储的数据对象
        let storage = this.getStorage()
        //然后将新的键值对添加到该对象中
        storage[key] = val
        //最后将新的对象转换为字符串并存储到 localStorage 中,即这种形式:namespace{{"key":"val"}}
        window.localStorage.setItem(namespace, JSON.stringify(storage))
    },

    getStorage() {
        try {  
            // 获取localStorage 中namespace里数据,如果没有返回空对象  
            return JSON.parse(window.localStorage.getItem(namespace) || '{}');  
        } catch (e) {  
            // 如果解析失败,返回空对象  
            console.error(e);  
            return {};  
        } 
    },

    getItem(key) {
        return this.getStorage()[key]
    },

    clearItem(key) {
        let storage = this.getStorage()
        delete storage[key]
        window.localStorage.setItem(namespace, JSON.stringify(storage))
    },

    //清除local
    clearAll() {
        window.localStorage.removeItem(namespace)
    }
}

数据可视化展示

在首页中,使用echart对订单数据进行可视化展示

在这里插入图片描述

组件高度复用

在移动端进行订单创建时,虽然每个功能不一样,但是页面中大部分展现情况还是一样,只用编写一个创建页面的组件,在跳转到创建页面,根据每个功能的需要对组件相关内容展示或者隐藏,实现组件的高度复用,提升开发效率

懒加载提高用户体验

在vue3种,懒加载实现的方式有很多种,例如各种UI框架内封装的懒加载组件,或者自己写一个懒加载的组件进行展示。
这个项目中使用第三方库vue-content-loader+svg绘制网站

vue-content-loader的好处是可以根据页面情况进行绘制,实现自定义的效果

<template>
     <content-loader
    viewBox="0 0 400 760"
    :speed="2"
    primaryColor="#f3f3f3"
    secondaryColor="#ecebeb"
  >
    <rect x="13" y="7" rx="0" ry="0" width="140" height="35" /> 
    <rect x="176" y="8" rx="0" ry="0" width="259" height="31" /> 
    <rect x="14" y="64" rx="0" ry="0" width="384" height="37" /> 
    <rect x="15" y="124" rx="0" ry="0" width="376" height="92" /> 
    <circle cx="51" cy="268" r="38" /> 
    <circle cx="191" cy="301" r="6" /> 
    <circle cx="147" cy="267" r="38" /> 
    <circle cx="243" cy="267" r="38" /> 
    <circle cx="342" cy="266" r="38" /> 
    <rect x="15" y="333" rx="0" ry="0" width="156" height="91" /> 
    <rect x="221" y="329" rx="0" ry="0" width="156" height="91" /> 
    <rect x="32" y="445" rx="0" ry="0" width="107" height="84" /> 
    <rect x="198" y="451" rx="0" ry="0" width="149" height="28" /> 
    <rect x="255" y="490" rx="0" ry="0" width="85" height="16" /> 
    <rect x="203" y="518" rx="0" ry="0" width="128" height="15" /> 
    <rect x="206" y="489" rx="0" ry="0" width="31" height="19" /> 
    <rect x="34" y="559" rx="0" ry="0" width="107" height="84" /> 
    <rect x="200" y="565" rx="0" ry="0" width="149" height="28" /> 
    <rect x="257" y="604" rx="0" ry="0" width="85" height="16" /> 
    <rect x="205" y="632" rx="0" ry="0" width="128" height="15" /> 
    <rect x="208" y="603" rx="0" ry="0" width="31" height="19" /> 
    <rect x="38" y="674" rx="0" ry="0" width="107" height="84" /> 
    <rect x="261" y="719" rx="0" ry="0" width="85" height="16" /> 
    <rect x="209" y="747" rx="0" ry="0" width="128" height="15" /> 
    <rect x="212" y="718" rx="0" ry="0" width="31" height="19" />
  </content-loader>
  </template>
  
  <script>
  import { ContentLoader } from 'vue-content-loader';
  export default {
    components: {
      ContentLoader
    }
  };
  </script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值