vue中根据用户权限动态添加路由详解(后端返回角色 前端过滤)

124 阅读12分钟

codeleading.com/article/968…

blog.csdn.net/fengprogram…

blog.csdn.net/qq_23073811…

参考这个:blog.csdn.net/weixin_4481… blog.csdn.net/weixin_4481…

需要注意的坑:

最开始是无限循环,后面是那个权限有了但是路由有些渲染出来了,有些没渲染出来, 找了两天,路由的name和menu的key的名字有些一致有些不一致,const role = roles.find(role => auth.funcCode == role[0]);这一步权限判断的时候,路由的name和menu的key不一致会判断失误。

无限循环那个必须要加vuex存储的一个判断,不然会死循环

目录如下:

1690424479893.png

1690424542277.png

menu.js 菜单

import { getChannelList } from '@/services/core'
import { getHisDeptList } from '@/services/hospital'
import { getUserContext } from '@/utils/userContext'
import auth from '@/utils/auth'


let _init = false;

let _menu = [

  // 仪表盘
  { level: 1, key: 'dashboard', name: '全院总览', url: '/dashboard', auth: [['dashboard', 'r']] },

  // 预约
  { level: 1, key: 'appoint', name: '预约', },
  { level: 2, parent: 'appoint', key: 'appoint_consultor', name: '上门邀约', icon: 'el-icon-chat-dot-square', url: '/appoint/to_hospital_appoint', auth: [['to_hos_appoint', 'r']] },
  { level: 2, parent: 'appoint', key: 'appointing', name: '科室预约', icon: 'el-icon-timer', url: '/appoint/appointing', auth: [['appointing', 'r']] },
  { level: 2, parent: 'appoint', key: 'appoint_settings', name: '设置', icon: 'el-icon-setting' },
  { level: 3, parent: 'appoint_settings', key: 'frequency', url: '/appoint/frequency', name: '班次设置', auth: [['appoint_setting', 'm']] },
  { level: 3, parent: 'appoint_settings', key: 'scheduling', name: '排班', url: '/appoint/schedule', auth: [['appoint_setting', 's']] },
  { level: 3, parent: 'appoint_settings', key: 'apt_subject', url: '/appoint/apt_subject', name: '预约设置', auth: [['appoint_setting', 'm']] },

  // 前厅
  { level: 1, key: 'todo', name: '前厅', },
  { level: 2, key: 'medicalGuide', parent: 'todo', name: '导医台', icon: 'el-icon-guide', url: '/lobby/guide', auth: [['guide', 'r']] },
  { level: 2, key: 'counsellingRoom', parent: 'todo', name: '接诊洽谈', icon: 'el-icon-chat-line-square', url: '/lobby/consulting', auth: [['consult', 'r']] },

  // 财务
  { level: 1, key: 'finance', name: '收银' },
  { level: 2, parent: 'finance', key: 'bill', name: '收银', icon: 'el-icon-shopping-cart-full', url: '/finance/bill', auth: [['checkout', 'r']] },

  { level: 2, parent: 'finance', key: 'coupon', name: '优惠券' },
  { level: 3, parent: 'coupon', key: 'cash_coupon', name: '代金券发券', url: '/finance/cash_coupon', auth: [['coupon', 'r']] },

  { level: 2, parent: 'finance', key: 'clearing', name: '日结' },
  { level: 3, parent: 'clearing', key: 'daily_check', name: '收银日结', icon: 'el-icon-money', url: '/finance/daily_check', auth: [['clearing', 'c']] },
  { level: 3, parent: 'clearing', key: 'daily_knot', name: '划扣日结', icon: 'el-icon-check', url: '/finance/daily_knot', auth: [['clearing', 'i']] },

  // 科室中心
  { level: 1, key: 'his_dept', name: '科室', },

  // 回访中心
  { level: 1, key: 'revisit', name: '回访', },
  { level: 2, parent: "revisit", key: 'revisit_plan', name: '回访任务' },
  { level: 3, parent: 'revisit_plan', key: 'revisit_task', url: '/revisit/mytask', name: '我的任务', auth: [['do_revisit', 'r'], ['allot_revisit', 'w']] },

  { level: 2, parent: "revisit", key: 'revisit_settings', name: '回访设置' },
  { level: 3, parent: "revisit_settings", key: 'revisit_group', url: "/revisit/group", name: '回访包管理', auth: [['revisit_setting', 'w']] },
  // { level: 3, parent: "revisit_settings", key: 'revisit_category_group', url: "/revisit/categoryGroup", name: '类别回访设置', auth: [['revisit_setting', 'w']] },
  // { level: 3, parent: "revisit_settings", key: 'revisit_assistant', url: "/revisit/assistant", name: '绑定专家助理', auth: [['revisit_setting', 'w']] },
  { level: 3, parent: "revisit_settings", key: 'revisit_result', url: "/revisit/planResult", name: '回访结果设置', auth: [['revisit_setting', 'w']] },
  { level: 3, parent: "revisit_settings", key: 'revisit_type', url: "/revisit/visitType", name: '回访参数设置', auth: [['revisit_setting', 'w']] },

  { level: 2, parent: "revisit", key: 'revisit_sms', name: '短信' },
  { level: 3, parent: "revisit_sms", key: 'revisit_sms_sendRecord', url: "/sms/smsRecord", name: '短信查询', auth: [['sms_setting', 'r']] },
  { level: 3, parent: "revisit_sms", key: 'revisit_sms_template', url: "/sms/template", name: '模板设置', auth: [['sms_setting', 'm']] },

  // 渠道
  { level: 1, key: 'channel', name: '渠道', },
  { level: 2, parent: 'channel', key: 'channel_customer', name: '我的客户', icon: 'el-icon-user', url: '/channel/my_customer', auth: [['channel_customer', 'r']] },
  { level: 2, parent: 'channel', key: 'channel_create', name: '渠道建档', icon: 'el-icon-edit-outline' },

  // 客户
  { level: 1, key: 'customer_center', name: '客户中心', url: '/customer_center', auth: [['customer_info', 'r'], ['allot_revisit', 'a']], },

  // 报表
  { level: 1, key: 'report', name: '报表', },

  { level: 2, parent: 'report', key: 'report_customer', name: '客户报表' },
  { level: 3, parent: 'report_customer', key: 'rpt_ctm_to_hospital_detail', name: '101 客户门诊明细表', url: '/report/ctm_to_hospital_detail', auth: [['report_ctm', 'c']] },
  { level: 3, parent: 'report_customer', key: 'rpt_ctm_payment_detail', name: '102 客户消费明细表', url: '/report/ctm_payment_detail', auth: [['report_ctm', 'a']] },
  { level: 3, parent: 'report_customer', key: 'rpt_ctm_execute_detail', name: '103 客户划扣明细表', url: '/report/ctm_execute_detail', auth: [['report_ctm', 'b']] },
  { level: 3, parent: 'report_customer', key: 'rpt_ctm_pdu_report', name: '104 客户剩余项目', url: '/report/ctm_pdu_report', auth: [['report_ctm', 'd']] },
  { level: 3, parent: 'report_customer', key: 'rpt_ctm_intention', name: '105 客户意向报表', url: '/report/ctm_intention', auth: [['report_ctm', 'g']] },
  { level: 3, parent: 'report_customer', key: 'rpt_ctm_to_hospital_cycle', name: '客户上门周期表', url: '/report/ctm_to_hospital_cycle', auth: [['report_ctm', 'e']] },
  { level: 3, parent: 'report_customer', key: 'rpt_ctm_classification', name: '客户分类报表', url: '/report/ctm_classification', auth: [['report_ctm', 'f']] },
  { level: 3, parent: 'report_customer', key: 'rpt_ctm_deposit_unused', name: '预收金未使用报表', url: '/report/ctm_deposit_unused', auth: [['report_ctm', 'h']] },

  { level: 2, parent: 'report', key: 'report_hospital', name: '运营报表' },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_consultor_achieve', name: '301 客服经理业绩报表', url: '/report/his_consultor_achieve', auth: [['report_his', 'a']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_consultor_achieve55', name: '355 客服经理业绩报表', url: '/report/his_consultor_achieve55', auth: [['report_his', '5']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_consultor_cat2_summary', name: '013 客服经理二级项目报表', url: '/report/his_consultor_cat2_summary', auth: [['report_his', 'd']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_developer_achieve2', name: '303 开发人业绩报表', url: '/report/his_developer_channel_achieve', auth: [['report_his', 'f']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_developer_cat2_summary', name: '开发人二级项目报表', url: '/report/his_developer_cat2_summary', auth: [['report_his', 'g']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_channel_summary', name: '802 渠道综合报表', url: '/report/his_channel_summary', auth: [['report_his', 'b']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_channel_cat2_summary', name: '014 渠道二级项目报表', url: '/report/his_channel_cat2_summary', auth: [['report_his', 'e']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_dept_summary', name: '804 科室业绩报表', url: '/report/his_dept_summary2', auth: [['report_his', 'c']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_dept_payment_detail', name: '308 科室二开报表', url: '/report/his_dept_payment_detail', auth: [['report_his', 'h']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_recommender_detail', name: '310 老带新报表', url: '/report/his_recommender_detail', auth: [['report_his', 'i']] },
  { level: 3, parent: 'report_hospital', key: 'rpt_his_operation_sign', name: '划扣签名报表', url: '/report/his_operation_sign', auth: [['report_his', 'z']] },


  { level: 2, parent: 'report', key: 'report_opt', name: '统一运营报表' },
  { level: 3, parent: 'report_opt', key: 'rpt_opt_001', name: 'O001 全院经营漏斗分析', url: '/report/O001', auth: [['report_opt', 'a']] },
  { level: 3, parent: 'report_opt', key: 'rpt_opt_008', name: 'O008 咨询经营漏斗分析', url: '/report/O008', auth: [['report_opt', 'b']] },
  { level: 3, parent: 'report_opt', key: 'rpt_opt_015', name: 'O015 产品经营漏斗分析', url: '/report/O015', auth: [['report_opt', 'c']] },
  { level: 3, parent: 'report_opt', key: 'rpt_opt_022', name: 'O022 渠道经营漏斗分析', url: '/report/O022', auth: [['report_opt', 'd']] },

  { level: 2, parent: 'report', key: 'report_finance', name: '财务报表' },
  { level: 3, parent: 'report_finance', key: 'rpt_fa_01', name: 'F01 财务综合报表', url: '/report/F01', auth: [['report_fin', 'a']] },
  { level: 3, parent: 'report_finance', key: 'report_fin_cash_detail', name: '收银日结报表', url: '/report/fin_cash_detail', auth: [['report_fin', 'b']] },
  { level: 3, parent: 'report_finance', key: 'report_fin_three_payment_method', name: '三方支付报表', url: '/report/fin_three_payment_method', auth: [['report_fin', 'c']] },
  { level: 3, parent: 'report_finance', key: 'report_fin_deposit', name: '预收金使用报表', url: '/report/fin_deposit_detail', auth: [['report_fin', 'd']] },
  { level: 3, parent: 'report_finance', key: 'report_fin_income_ext', name: '划扣补充报表', url: '/report/fin_income_ext', auth: [['report_fin', 'f']] },

  { level: 2, parent: 'report', key: 'report_revisit', name: '回访报表' },
  { level: 3, parent: 'report_revisit', key: 'report_result', name: '回访结果分析表', url: '/report/revisit/rptResult', auth: [['report_rev', 'a']] },
  { level: 3, parent: 'report_revisit', key: 'report_visit_count', name: '人员回访统计表', url: '/report/revisit/RptVisitUserCount', auth: [['report_rev', 'c']] },

  //运营中心
  { level: 1, key: 'operate', name: '运营', },
  { level: 2, parent: 'operate', key: 'pdu_manage', name: '产品管理' },
  { level: 3, parent: 'pdu_manage', key: 'product', url: '/operate/product', name: '产品设置', auth: [['pdu', 'r']] },
  { level: 3, parent: 'pdu_manage', key: 'product_set', url: '/operate/product_set', name: '套餐设置', auth: [['pdu', 'r']] },
  { level: 3, parent: 'pdu_manage', key: 'storing_card', url: '/operate/storing_card_def', name: '储值卡设定', auth: [['marketing', 'r']] },

  // { level: 2, parent: 'operate', key: 'activity_manage', name: '活动管理' },
  // { level: 3, parent: 'activity_manage', key: 'cash_coupon_def', name: '代金券设定', url: '/operate/cash_coupon_def', auth: [['marketing', 'r']] },
  // { level: 3, parent: 'activity_manage', key: 'full_discount_def', name: '满减活动设定', url: '/operate/full_discount_def', auth: [['marketing', 'r']] },

  { level: 2, parent: 'operate', key: 'cus_manager', name: '客户管理' },
  { level: 3, parent: 'cus_manager', key: 'indentures', url: '/operate/indentures', name: '客户归属改派', auth: [['ctm_indenture_chg', 'r']] },
  { level: 3, parent: 'cus_manager', key: 'indentures_his', url: '/operate/indentures_his', name: '客户归属改派记录', auth: [['ctm_indenture_chg', 'r']] },
  { level: 3, parent: 'cus_manager', key: 'channelChage', url: '/operate/channelChage', name: '客户渠道改派', auth: [['ctm_channel_chg', 'r']] },
  { level: 3, parent: 'cus_manager', key: 'channelChage_his', url: '/operate/channelChage_his', name: '客户渠道改派记录', auth: [['ctm_channel_chg', 'r']] },

  // 会员中心
  { level: 1, key: 'member', name: '会员' },

  { level: 2, parent: 'member', key: 'membership', name: '会员管理' },
  { level: 3, parent: 'membership', key: 'member_mgr', name: '会籍管理', icon: 'el-icon-s-custom', url: '/member/membership', auth: [['member_info', 'r']] },
  { level: 3, parent: 'membership', key: 'member_level', name: '会员等级', icon: 'el-icon-medal', url: '/member/member_level', auth: [['member_info', 'r']] },
  { level: 3, parent: 'membership', key: 'member_up', name: '会员升降级', icon: 'el-icon-data-analysis', url: '/member/member_up', auth: [['member_info', 'r']] },

  { level: 2, parent: 'member', key: 'member_loyalty', name: '忠诚度管理' },
  { level: 3, parent: 'member_loyalty', key: 'member_rfm', name: '人群细分-RFM', url: '/member/member_rfm', auth: [['member_info', 'r']] },
  { level: 3, parent: 'member_loyalty', key: 'member_point', name: '会员积分', url: '/member/member_point', auth: [['member_info', 'r']] },
  { level: 3, parent: 'member_loyalty', key: 'member_recommend', name: '老带新', url: '/member/member_recommend', auth: [['member_info', 'r']] },

  { level: 2, parent: 'member', key: 'member_lifecycle', name: '生命周期管理' },
  { level: 3, parent: 'member_lifecycle', key: 'member_large_cycle', name: '大生命周期管理', icon: 'el-icon-refresh-right', url: '/member/member_large_cycle', auth: [['member_info', 'r']] },

  { level: 2, parent: 'member', key: 'member_setting', name: '会员设置' },
  { level: 3, parent: 'member_setting', key: 'member_card', name: '会员卡设置', url: '/member/member_card', auth: [['member_setting', 'r']] },
  { level: 3, parent: 'member_setting', key: 'member_large_cycle_setting', name: '大生命周期计算规则', url: '/member/member_large_cycle_setting', auth: [['member_setting', 'r']] },
  { level: 3, parent: 'member_setting', key: 'member_point_setting', name: '积分赠送比例', url: '/member/member_point_setting', auth: [['member_setting', 'r']] },
  { level: 3, parent: 'member_setting', key: 'member_point_double', name: '积分翻倍活动', url: '/member/member_point_double', auth: [['member_setting', 'r']] },

  // 系统设置
  { level: 1, key: 'setting', name: '系统设置' },
  { level: 2, parent: 'setting', key: 'authorize', name: '系统授权' },
  { level: 3, parent: 'authorize', key: 'account', name: '账号', url: '/setting/account', auth: [['account', 'r']] },
  { level: 3, parent: 'authorize', key: 'post', name: '岗位', url: '/setting/post', auth: [['position', 'r']] },
  { level: 3, parent: 'authorize', key: 'auth', name: '功能', url: '/setting/auth', auth: [['position', 'r']] },
  { level: 2, parent: 'setting', key: 'deptmanage', name: '科室设置' },
  { level: 3, parent: 'deptmanage', key: 'dept', name: '科室', url: '/setting/dept', auth: [['main_data', 'm']] },
  { level: 2, parent: 'setting', key: 'finance_mgr', name: '财务设置' },
  { level: 3, parent: 'finance_mgr', key: 'payment_method', name: '支付方式', url: '/setting/pay', auth: [['main_data', 'm']] },
  { level: 2, parent: 'setting', key: 'channel_mgr', name: '渠道' },
  { level: 3, parent: 'channel_mgr', key: 'channel_method', name: '渠道设置', url: '/setting/channel', auth: [['main_data', 'm']] },
  { level: 2, parent: 'setting', key: 'options', name: '系统设置', url: '/setting/options', auth: [['system_set', 'd']] },
]

export async function menu() {
  if (_init) return _menu;

  const userContext = getUserContext()
  const allowedHisDepts = userContext.userDTO.hisDepts ? userContext.userDTO.hisDepts.split(",") : []
  const allowedChannels = userContext.userDTO.channels ? userContext.userDTO.channels.split(",") : []

  // 渠道权限
  const channels = await getChannelList()
  const channelMap = new Map(channels.map(x => [x.id, x]))

  for (let channel of channels) {
    if (allowedChannels.indexOf(channel.id.toString()) < 0) continue;
    channel.allowed = true

    let parent = channelMap.get(channel.parentId)

    while (parent) {
      parent.allowed = true
      parent = channelMap.get(channel.parentId)
    }
  }

  const channelsMenu = channels
    .filter(x => x.sourceLevel === 1 && x.allowed)
    .map(x => ({ level: 3, parent: 'channel_create', key: x.id.toString(), name: x.name, url: `/channel/${x.id}`, auth: [['channel_customer', 'w']] }))

  const channelIndex = _menu.findIndex(x => x.key === 'channel')
  _menu.splice(channelIndex, 0, ...channelsMenu)

  // 科室权限
  const hisDepts = await getHisDeptList()

  for (let hisDept of hisDepts) {
    if (allowedHisDepts.indexOf(hisDept.hisDeptId) < 0) continue;

    _menu.push({
      level: 2,
      parent: "his_dept",
      key: hisDept.hisDeptId,
      name: hisDept.hisDeptName,
      url: `/his_dept/${hisDept.hisDeptId}`,
      auth: [
        ["clinic", "r"],
        ["his_dept_billing", "r"],
        ["treatment", "r"],
        ["medicine", "r"],
      ],
    });
  }

  // 权限筛选
  for (let item of _menu) {
    if (!item.auth) continue;

    item.enabled =
      item.auth.map((x) => auth(x[0], x[1])).filter((x) => x).length > 0;
  }

  // console.log('menu init', _menu)
  _init = true
  
  return _menu;
}



report.js 权限路由

export default [
  /********************************************************************************************************
   * 客户报表
   */

  // 客户门诊明细表
  {
    name: "rpt_ctm_to_hospital_detail",
    path: "/report/ctm_to_hospital_detail",
    component: () => import( /* webpackChunkName: "report" */ "@/views/report/customer/ctm_to_hospital_detail"),
    meta:{requiresAuth: true,roles:[["report_ctm","c"]]}
  },
  // 客户消费明细表
  {
    name: "rpt_ctm_payment_detail",
    path: "/report/ctm_payment_detail",
    component: () => import( /* webpackChunkName: "report" */ "@/views/report/customer/ctm_payment_detail"),
    meta:{requiresAuth: true,roles:[["report_ctm","a"]]}
  },
  // 客户划扣明细表
  {
    name: "rpt_ctm_execute_detail",
    path: "/report/ctm_execute_detail",
    component: () => import( /* webpackChunkName: "report" */ "@/views/report/customer/ctm_execute_detail"),
    meta:{requiresAuth: true,roles:[["report_ctm","b"]]}
  },

  // 客户未划扣明细表
  {
    name: "rpt_ctm_pdu_report",
    path: "/report/ctm_pdu_report",
    component: () => import( /* webpackChunkName: "report" */ "@/views/report/customer/ctm_pdu_report"),
    meta:{requiresAuth: true,roles:[["report_ctm","d"]]}
  },

  // 客户意向报表
  {
    name: "rpt_ctm_intention",
    path: "/report/ctm_intention",
    component: () => import( /* webpackChunkName: "report" */ "@/views/report/customer/ctm_intention"),
    meta:{requiresAuth: true,roles:[["report_ctm","g"]]}
  },

  // 客户上门周期表
  {
    name: "rpt_ctm_to_hospital_cycle",
    path: "/report/ctm_to_hospital_cycle",
    component: () => import( /* webpackChunkName: "report" */ "@/views/report/customer/ctm_to_hospital_cycle"),
    meta:{requiresAuth: true,roles:[["report_ctm","e"]]}
  },

  // 客户分类报表
  {
    name: "rpt_ctm_classification",
    path: "/report/ctm_classification",
    component: () => import( /* webpackChunkName: "report" */ "@/views/report/customer/ctm_classification"),
    meta:{requiresAuth: true,roles:[["report_ctm","f"]]}
  },

  // 预收金未使用报表
  {
    name: "rpt_ctm_deposit_unused",
    path: "/report/ctm_deposit_unused",
    component: () => import( /* webpackChunkName: "report" */ "@/views/report/customer/ctm_deposit_unused"),
    meta:{requiresAuth: true,roles:[["report_ctm","h"]]}
  },


  /********************************************************************************************************
   * 运营报表
   */

  // 客服经理业绩报表
  {
    name: 'rpt_his_consultor_achieve',
    path: '/report/his_consultor_achieve',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_consultor_achieve'),
    meta:{requiresAuth: true,roles:[["report_his","a"]]}
  },
  // 客服经理业绩报表55开
  {
    name: 'rpt_his_consultor_achieve55',
    path: '/report/his_consultor_achieve55',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_consultor_achieve55'),
    meta:{requiresAuth: true,roles:[["report_his","5"]]}
  },
  // 客服经理二级项目报表
  {
    name: 'rpt_his_consultor_cat2_summary',
    path: '/report/his_consultor_cat2_summary',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_consultor_cat2_summary'),
    meta:{requiresAuth: true,roles:[["report_his","d"]]}
  },
  // 开发人业绩报表
  {
    name: 'rpt_his_developer_achieve2',
    // name: 'rpt_his_developer_channel_achieve',
    path: '/report/his_developer_channel_achieve',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_developer_channel_achieve'),
    meta:{requiresAuth: true,roles:[["report_his","f"]]}
  },
  // 开发人二级业绩报表
  {
    name: 'rpt_his_developer_cat2_summary',
    path: '/report/his_developer_cat2_summary',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_developer_cat2_summary'),
    meta:{requiresAuth: true,roles:[["report_his","g"]]}
  },
  // 渠道综合报表
  {
    name: 'rpt_his_channel_summary',
    path: '/report/his_channel_summary',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_channel_summary'),
    meta:{requiresAuth: true,roles:[["report_his","b"]]}
  },
  // 渠道二级项目报表
  {
    name: 'rpt_his_channel_cat2_summary',
    path: '/report/his_channel_cat2_summary',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_channel_cat2_summary'),
    meta:{requiresAuth: true,roles:[["report_his","e"]]}
  },
  // 804报表
  {
    name: 'rpt_his_dept_summary',
    path: '/report/his_dept_summary2',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_dept_summary2'),
    meta:{requiresAuth: true,roles:[["report_his","c"]]}
  },
  // 科室二开明细表
  {
    name: "rpt_his_dept_payment_detail",
    path: "/report/his_dept_payment_detail",
    component: () => import( /* webpackChunkName: "report" */ "@/views/report/hospital/his_dept_payment_detail"),
    meta:{requiresAuth: true,roles:[["report_his","h"]]}
  },
  // 老带新报表
  {
    name: 'rpt_his_recommender_detail',
    path: '/report/his_recommender_detail',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_recommender_detail'),
    meta:{requiresAuth: true,roles:[["report_his","i"]]}
  },
  // 划扣签名报表
  {
      name: 'rpt_his_operation_sign',
      path: '/report/his_operation_sign',
      component: () => import(/* webpackChunkName: "report" */ '@/views/report/hospital/his_operation_sign'),
      meta:{requiresAuth: true,roles:[["report_his","z"]]}
  },
  // // 退款明细表
  // {
  //   name: 'report_ctm_refund',
  //   path: '/report/ctm/ctm_refund',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/ctm/ctm_refund'),
  // },
  // // 产品渠道贡献报表
  // {
  //   name: 'report_pdu_channel',
  //   path: '/report/ctm/pdu_channel',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/ctm/pdu_channel'),
  // },
  // // 产品购买排行
  // {
  //   name: 'report_pdu_rank',
  //   path: '/report/ctm/pdu_rank',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/ctm/pdu_rank'),
  // },

  // // 分诊消费明细表
  // {
  //   name: 'report_register_pay',
  //   path: '/report/ctm/register_pay',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/ctm/register_pay'),
  // },
  // // 客户区域报表
  // {
  //   name: 'report_ctm_location',
  //   path: '/report/ctm/ctm_location',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/ctm/ctm_location'),
  // },
  // // 医院运营报表
  // {
  //   name: 'report_hospital_op',
  //   path: '/report/ctm/hospital_op',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/ctm/hospital_op'),
  // },
  /********************************************************************************************************
   * 统一运营报表
   */

  // O001 全院经营漏斗分析
  {
    name: 'rpt_opt_001',
    path: '/report/O001',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/opt/opt_001'),
    meta:{requiresAuth: true,roles:[['report_opt', 'a']]}
  },
  // O008 咨询经营漏斗分析
  {
    name: 'rpt_opt_008',
    path: '/report/O008',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/opt/opt_008'),
    meta:{requiresAuth: true,roles:[['report_opt', 'b']]}
  },
  // O015 产品经营漏斗分析
  {
    name: 'rpt_opt_015',
    path: '/report/O015',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/opt/opt_015'),
    meta:{requiresAuth: true,roles:[['report_opt', 'c'],['pdu', 'r']]}
  },
  // O022 产品经营漏斗分析
  {
    name: 'rpt_opt_022',
    path: '/report/O022',
    component: () => import(/* webpackChunkName: "report" */ '@/views/report/opt/opt_022'),
    meta:{requiresAuth: true,roles:[['report_opt', 'd']]}
  },
  // // 客户到院报表
  // {
  //   name: 'report_ctm_to_hospital',
  //   path: '/report/frontend/ctm_to_hospital',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/frontend/ctm_to_hospital'),
  // },
  // // 网电报表
  // {
  //   name: 'report_frontend_report',
  //   path: '/report/frontend/frontend_report',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/frontend/frontend_report'),
  // },
  // // 第一需求分析报表
  // {
  //   name: 'report_ctm_first_require',
  //   path: '/report/frontend/ctm_first_require',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/frontend/ctm_first_require'),
  // },
  // // 渠道分析报表
  // {
  //   name: 'report_ctm_channel',
  //   path: '/report/frontend/ctm_channel',
  //   component: () => import(/* webpackChunkName: "report" */ '@/views/report/frontend/ctm_channel'),
  // },

  /********************************************************************************************************
   * 财务报表
   */

  // 财务综合报表
  {
    name: 'rpt_fa_01',
    path: "/report/f01",
    component: () =>
      import(
        /* webpackChunkName: "report" */ "@/views/report/finance/fa_01"
      ),
      meta:{requiresAuth: true,roles:[['report_fin', 'a']]}
  },
  // 收银日结报表
  {
    name: "report_fin_cash_detail",
    path: "/report/fin_cash_detail",
    component: () =>
      import(
        /* webpackChunkName: "report" */ "@/views/report/finance/fin_cash_detail"
      ),
      meta:{requiresAuth: true,roles:[['report_fin', 'b']]}
  },
  //电商第三方支付方式报表
  {
    name: "report_fin_three_payment_method",
    path: "/report/fin_three_payment_method",
    component: () =>
      import(
        /* webpackChunkName: "report" */ "@/views/report/finance/fin_three_payment_method"
      ),
      meta:{requiresAuth: true,roles:[['report_fin', 'c']]}
  },

  // 预收明细
  {
    name: "report_fin_deposit",
    path: "/report/fin_deposit_detail",
    component: () =>
      import(
        /* webpackChunkName: "report" */ "@/views/report/finance/fin_deposit_detail"
      ),
      meta:{requiresAuth: true,roles:[['report_fin', 'd']]}
  },

  // 营收补充报表
  {
    name: "report_fin_income_ext",
    path: "/report/fin_income_ext",
    component: () =>
      import(
        /* webpackChunkName: "report" */ "@/views/report/finance/fin_income_ext"
      ),
      meta:{requiresAuth: true,roles:[['report_fin', 'f']]}
  },

  /********************************************************************************************************
   * 回访报表
   */

  // 回访结果分析表
  {
    name: "report_result",
    path: "/report/revisit/rptResult",
    component: () =>
      import(/* webpackChunkName: "report" */ "@/views/revisit/rpt/RptResult"),
      meta:{requiresAuth: true,roles:[['report_rev', 'a']]}
  },

  // 回访消费情况明细表
  // {
  //   name: "report_visit_consum",
  //   path: "/report/revisit/RptVisitConsum",
  //   component: () =>
  //     import(
  //       /* webpackChunkName: "report" */ "@/views/revisit/rpt/RptVisitConsum"
  //     ),
  // },

  // 人员回访统计表
  {
    name: "report_visit_count",
    path: "/report/revisit/RptVisitUserCount",
    component: () =>
      import(
        /* webpackChunkName: "report" */ "@/views/revisit/rpt/RptVisitUserCount"
      ),
      meta:{requiresAuth: true,roles:[['report_rev', 'c']]}
  },
];



index.js 所有路由
router.beforeEach路由守卫,权限在这个函数里判断

import Vue from 'vue';
import VueRouter from 'vue-router';

import Home from '@/views/auth/home';
import Login from '@/views/auth/login';

import channel from './channel';
import operate from './operate';
import depart from './depart';
import setting from './setting';
import customer from './customer';
import lobby from './lobby';  //前厅中心
import revisit from './revisit';  //前厅中心
import finance from './finance';
import appoint from './appoint';
import member from './member';
import report from './report';
import Stomp from '../assets/js/stomp'
import store from '../store'

Vue.use(VueRouter);
Vue.prototype.Stomp = Stomp
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push(location) {
  return originalPush.call(this, location).catch(err => err)
}
// const router = new VueRouter({
//   mode: "history",
//   routes: [

    
//   ],
// });


export const routes = [
  {
    name: 'Home',
    path: '/',
    component: Home,
  },
  {
    name: 'Login',
    path: '/login',
    component: Login,
    meta: {
      singlePage: true,
    }
  },
  {
    name: 'uniquery',
    path: '/uniquery/:viewName/:queryId',
    props: true,
    component: () => import(/* webpackChunkName: "components" */ '@/components/uniquery/QueryEditor'),
    meta: {
      singlePage: true,
      unKeepLive: true,
    }
  },
  {
    name: '404',
    path: '/404',
    props: true,
    component: () => import(/* webpackChunkName: "components" */ '@/views/components/404'),
    meta: {
      singlePage: true,
      unKeepLive: true,
    }
  },
  {
    name: 'noPermis',
    path: '/noPermis',
    props: true,
    component: () => import(/* webpackChunkName: "components" */ '@/views/components/noPermis'),
    meta: {
      singlePage: true,
      unKeepLive: true,
    }
  },
  {
    name: 'noPower',
    path: '/noPower',
    props: true,
    component: () => import(/* webpackChunkName: "components" */ '@/views/components/noPower'),
  },
  // // 仪表盘
  // {
  //   name: 'dashboard',
  //   path: '/dashboard',
  //   component: () => import(/* webpackChunkName: "components" */ '@/views/dashboard'),
  // },

  // ...channel,

  // ...customer,
  // 这个菜单里没有  客户中心  customer里的  
  {
    name: 'customer-clinical',
    path: '/customer-clinical',
    component: () => import(/* webpackChunkName: "channel" */ '@/views/customer-detail/customer-clinical.vue'),
  },
  
  // ...operate,
  // 这个菜单里没有  operate里的
  {
    name: 'contact_logs',
    path: '/operate/contact_logs',
    component: () => import(/* webpackChunkName: "setting" */ '@/views/operate/cus_manager/contact_logs/index'),
  },
  
  // ...depart,

  // ...setting,
  // 这个菜单里没有  setting里的
  {
    name: 'demo',
    path: '/setting/demo',
    component: () => import(/* webpackChunkName: "setting" */ '@/views/setting/demo'),
  },

  // ...lobby,
  // ...revisit,
  // ...finance,
  // ...appoint,

  // ...member,
  // 这个菜单里没有  member里的
  {
    name: 'member_overview',
    path: '/member/overview',
    component: () => import(/* webpackChunkName: "operate" */ '@/views/member/overview'),
  },
  // 这个菜单里没有  member里的
  {
    name: 'member_small_cycle',
    path: '/member/member_small_cycle',
    component: () => import(/* webpackChunkName: "operate" */ '@/views/member/small_cycle'),
    meta:{requiresAuth: true,roles:[["report_ctm","c"]]}
  },
  // ...report,
]

export const asyncRouter = [
  // 仪表盘
  {
    name: 'dashboard',
    path: '/dashboard',
    component: () => import(/* webpackChunkName: "components" */ '@/views/dashboard'),
    meta:{requiresAuth: true,roles:[['dashboard', 'r']]}
  },
  ...channel,
  ...customer,
  ...operate,
  ...depart,
  ...setting,
  ...lobby,
  ...revisit,
  ...finance,
  ...appoint,
  ...member,
  ...report,
]

const router = new VueRouter({
  mode: "history",
  routes
})


router.beforeEach((to, from, next) => {
  let token = localStorage.getItem("token");
  if(token){
    let userContext = localStorage.getItem("userContext")
    let auths = JSON.parse(userContext).auths
    let asyncRouterRoles = []
    let authsAry = []
    let asyncPath = []
    asyncRouter.forEach((v)=>{
      asyncPath.push(v.path)
      if(v.meta && v.meta.roles){
        asyncRouterRoles.push(v.meta.roles[0])
      }
    })
    auths.forEach((v,i)=>{
      authsAry.push(v.funcCode)
    })

    function compare(arr1,arr2){
      return arr1.filter((v)=>{
        return arr2.includes(v)
      })
    }
    // let roles = ["report_ctm","report_his","report_opt","report_fin","report_rev"]
    let roles = compare(authsAry, asyncRouterRoles) 
    if(store.state.addRouters.length){
      let routersPath = []
      store.state.Routers.forEach((v)=>{
        routersPath.push(v.path)
      })
      if( /^\/channel\/\d/.test(to.path) || /^\/his_dept\/\d/.test(to.path) || to.path.includes('/uniquery/') ){
        next()
        return
      }
      if(!routersPath.includes(to.path)){
        next({path:'/noPermis'}) // 没有权限
        return
      }
      // console.log("yes")
      next()
    }else{
      // console.log("not")
      store.dispatch("asyncGetRouter",{roles}).then(res=>{
        router.addRoutes(store.state.addRouters)
      })
      next({...to})
    }
  // next()
  }else{
    if(to.path=='/login'){
      next();
    }else{
      next({path:'/login'})
    }
    next()
  }
})

export default router;

store.js vuex:保存动态添加的路由
hasPermission函数处理有权限的路由


import Vue from "vue"
import Vuex from "vuex"
import router,{routes,asyncRouter} from "./router"
function hasPermission(route,roles){
  // console.log("route.meta.roles",route.meta.roles)
  let authTag
  if(route.meta && route.meta.roles){
    let userContext = localStorage.getItem("userContext")
    let auths = JSON.parse(userContext).auths
    let roles = route.meta.roles
    let authsAry = []
    auths.forEach((v,i)=>{
      authsAry.push(v.abilities)
    })
    let aa = roles.some((role)=>{
      if( route.meta.roles.indexOf(role)>-1 ){ 



        // auths.forEach((v,i)=>{
        //   console.log("v===",v)
        //   console.log("route.meta.roles",route.meta.roles)
        //   if(v.funcCode == route.meta.roles[0]){
        //     if(v.abilities.indexOf( route.meta.roles[1] ) > -1){
        //       authTag = true
        //     }
        //   }
        // })

        

        // for(var i=0;i<auths.length;i++){
        //   for(var j=0;j<roles.length;j++){
        //     if(auths[i].funcCode == roles[j][0]){
        //       if( auths[i].abilities.indexOf(roles[j][1]) > - 1 ){
        //         console.log("route.meta.roles",route.meta.roles)
        //         authTag = true
        //       }
        //     }
        //   }
        // }


        auths.forEach(auth=>{
          const role = roles.find(role => auth.funcCode == role[0]);
          console.log("roles.find",role)
          if(role&&auth.abilities.includes(role[1])){
            authTag = true;
          }
        })


      }
    })
    return authTag
  }else{
    // return true
    return false
  }
}


/*
  递归过滤异步路由表 返回符合用户角色的路由
  @param asyncRouter 异步路由
  @param roles 用户角色
*/
function filterAsyncRouter(asyncRouter, roles) {
  let filterRouter = asyncRouter.filter(route =>{
    if(hasPermission(route, roles)) {
      if(route.children && route.children.length) {
          route.children = filterAsyncRouter(route.children, roles)
      }
      return true 
    }
    return false
  })
  return filterRouter
}

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    addRouters:  [],
    Routers: []
  },
  mutations: {
    getRouter(state, paload) {
      // console.log(paload)
      state.Routers = routes.concat(paload)
      state.addRouters = paload
      // router.addRoutes(paload)
    }
  },
  actions: {
    asyncGetRouter({ commit }, data) {
      const { roles } = data
      return new Promise(resolve =>{
        let addAsyncRouters = filterAsyncRouter(asyncRouter, roles)
        console.log("addAsyncRouters========",addAsyncRouters)
        commit('getRouter', addAsyncRouters)
        resolve()
      })
    }
  }
})



以下文件不是主要逻辑,主要是获取权限的步骤,和渲染左侧菜单

api.js

//权限列表allAuthList
export const getAuthConfig = (params) => {
    // return http.get('/api/user/v1/auth/', params)
    return cache.get('authList', () => http.get('/api/user/v1/auth/'), true)
}

login.vue

1690425274273.png

Desktop.vue 渲染左侧菜单

由于我们的路由是平级处理的,这里需要单独处理二级,三级子菜单

这个页面的参考意义不大,根据项目做不同写法


<template>
  <div :class="`desktop ${leftMenu && leftMenu.length ? '' : 'no-aside'}`">
    <header class="nav-top">
      <div class="logo">
        <span class="logo-istar">iStar</span>
        <span>-CRM{{ region }}</span>
      </div>
      <el-menu
        :default-active="topActive"
        class="menu"
        mode="horizontal"
        @select="onTopMenuSelect"
      >
        <template v-for="item in menu">
          <!-- 一级菜单 -->
          <el-menu-item :key="item.key" :index="String(item.key)">
            <i :class="`menu-item-logo ${item.icon}`"></i>
            <b>{{ item.name }}</b>
          </el-menu-item>
        </template>
      </el-menu>

      <user-avatar class="ys-query_avatar" />
    </header>

    <aside v-if="leftMenu && leftMenu.length">
      <el-menu
        :default-active="leftActive"
        class="nav-left menu"
        @select="onLeftMenuSelect"
      >
        <template v-for="item in leftMenu">
          <!-- 一级菜单 -->
          <el-menu-item v-if="!item.children" :key="item.key" :index="item.key">
            <i :class="`menu-item-logo ${item.icon}`"></i>
            <b>{{ item.name }}</b>
          </el-menu-item>
          <!-- 一级子菜单 -->
          <el-submenu v-else :key="item.key" :index="item.key">
            <template slot="title">
              <i :class="`menu-item-logo ${item.icon}`"></i>
              <b>{{ item.name }}</b>
            </template>
            <template v-for="item2 in item.children">
              <!-- 二级菜单 -->
              <el-menu-item
                v-if="!item2.children"
                :key="item2.key"
                :index="item2.key"
              >
                <!-- <i :class="`menu-item-logo ${item2.icon}`"></i> -->
                <b style="margin-left: 10px">{{ item2.name }}</b>
              </el-menu-item>
              <!-- 二级菜单分组 -->
              <el-menu-item-group v-else :key="item2.key" :index="item2.key">
                <span class="menu-level2__title" slot="title">
                  {{ item2.name }}
                </span>
                <!-- 三级菜单 -->
                <el-menu-item
                  v-for="item3 in item2.children"
                  :key="item3.key"
                  :index="item3.key"
                >
                  <!-- <i :class="`menu-item-logo ${item3.icon}`"></i> -->
                  <b>{{ item3.name }}</b>
                </el-menu-item>
              </el-menu-item-group>
            </template>
          </el-submenu>
        </template>
      </el-menu>
    </aside>

    <keep-alive>
      <router-view></router-view>
    </keep-alive>
  </div>
</template>

<script>
import { menu, getChannelCascader } from "@/services/core";
import UserAvatar from "@/components/core/UserAvatar";
import { getSystemConfig } from "@/utils/userContext";
import { getUserContext } from "@/utils/userContext";
export default {
  components: {
    "user-avatar": UserAvatar,
  },

  data() {
    return {
      map: new Map(),
      routerMap: new Map(),
      menu: [],
      topActive: null,
      leftActive: null,
    };
  },

  async mounted() {
    await this.initMenu();
    this.redirect();
  },

  computed: {
    leftMenu() {
      if (!this.topActive) return [];

      const lv1 = this.map.get(this.topActive);
      if (!lv1) return [];

      return lv1.children;
    },

    region() {
      const { company_name } = getSystemConfig();
      let region = "";
      if (company_name === "武汉") {
        const {
          userDTO: { loginHospitalId },
        } = getUserContext();
        region =
          loginHospitalId === 1 ? "武昌" : loginHospitalId === 2 ? "汉口" : "";
      } else {
        region = company_name;
      }

      return `${region ? `-` + region : ""}`;
    },
  },

  watch: {
    $route: {
      async handler(matched) {
        if (!matched || !matched.name) return;

        let menuItem = this.routerMap.get(matched.name);

        if (!menuItem) return;

        // 缓存 url
        menuItem.currentUrl = matched.fullPath;

        this.leftActive = null;
        this.topActive = null;

        // 设置当前选中的菜单
        while (menuItem) {
          if (menuItem.level > 1 && this.leftActive === null) {
            switch (menuItem.route) {
              case "his_dept":
              case "channel":
                this.leftActive = matched.params.pathMatch;
                break;
              default:
                this.leftActive = menuItem.key;
                break;
            }

            const item = this.map.get(this.leftActive);
            this.getRootHistory(item);
          } else if (menuItem.level === 1) {
            this.topActive = menuItem.key;
          }

          menuItem = this.map.get(menuItem.parent);
        }

        if (!this.menu) await this.initMenu();
      },
      immediate: false,
    },
  },

  methods: {
    async initMenu() {
      let _menu = JSON.parse(JSON.stringify(await menu()));

      // 菜单整理成多层结构
      this.map = new Map(_menu.map((x) => [x.key, x]));

      // 三级菜单
      for (let item of _menu.filter((x) => x.level === 3)) {
        if (item.auth && !item.enabled) continue;

        const parent = this.map.get(item.parent);

        if (!parent) {
          console.error("menu error", item);
          continue;
        }

        if (!parent.children) parent.children = [];
        parent.children.push(item);
      }

      // 二级菜单
      for (let item of _menu.filter((x) => x.level === 2)) {
        if (
          !(
            (item.auth && item.enabled) ||
            (!item.auth && item.children && item.children.length)
          )
        )
          continue;

        const parent = this.map.get(item.parent);

        if (!parent) {
          console.error("menu error", item);
          continue;
        }

        if (!parent.children) parent.children = [];
        parent.children.push(item);
      }

      // 菜单与路由的映射关系
      for (let x of _menu) {
        if (!x.url) continue;

        const matched = this.$router.match(x.url);
        if (!matched.name) continue;

        x.route = matched.name;

        if (this.routerMap.get(matched.name)) continue;
        this.routerMap.set(matched.name, x);
      }

      this.menu = _menu.filter(
        (x) =>
          x.level === 1 &&
          ((x.auth && x.enabled) ||
            (!x.auth && x.children && x.children.length))
      );
    },

    // 根目录跳转设置
    async redirect() {
      const url = new URL(window.location.href);
      if (url.pathname !== "/") return;

      let index = 0;
      let menuItem = this.menu[index];

      while (menuItem) {
        if (menuItem.url) {
          this.$router.push(menuItem.url);
          console.log("redirect", menuItem.url);
          return;
        }

        if (menuItem.children && menuItem.children.length) {
          menuItem = menuItem.children[0];
        } else {
          index++;
          menuItem = this.menu[index];
        }
      }

      console.error("没有任何权限");
    },

    async onTopMenuSelect(key) {
      const item = this.map.get(key);

      if (item.url) {
        this.$router.push(item.currentUrl || item.url);
        return;
      }
      //历史选中
      if (item.history) {
        this.$router.push(item.history.currentUrl || item.history.url);
        return;
      }

      if (!item.children) return;

      for (let lv2 of item.children) {
        if (lv2.url) {
          this.$router.push(lv2.currentUrl || lv2.url);
          return;
        }

        if (!lv2.children) continue;

        for (let lv3 of lv2.children) {
          if (lv3.url) {
            this.$router.push(lv3.currentUrl || lv3.url);
            return;
          }
        }
      }
    },

    getRootHistory(item) {
      if (!item) {
        this.$router.push("/404");
        return;
      }

      if (item.parent) {
        const item2 = this.map.get(item.parent);
        if (item2.parent) {
          const item3 = this.map.get(item2.parent);
          item3["history"] = item;
        } else {
          item2["history"] = item;
        }
      }
    },

    async onLeftMenuSelect(key) {
      const item = this.map.get(key);
      this.leftActive = item.key;

      this.getRootHistory(item);
      this.$router.push(item.url);
    },
  },
};
</script>

<style lang="scss" scoped>
$menu-top: rpx(56);
$menu-left: rpx(170);
$menu-active: $primary;
$menu-bg-active: rgba($primary, 0.1);

.desktop {
  position: absolute;
  top: $menu-top;
  left: $menu-left;
  right: rpx(0);
  bottom: rpx(0);
  overflow: auto;

  &.no-aside {
    left: rpx(0);
  }
}

.nav-top {
  position: fixed;
  top: 0px;
  left: 0px;
  right: 0px;
  height: $menu-top;
  overflow: hidden;
  border-bottom: 1px solid $border-color;
  z-index: 100;
  display: flex;
  flex-flow: row nowrap;
  justify-content: flex-start;
  align-items: center;
  box-shadow: 0 0 10px #00000014;
  .logo {
    // width: $menu-left !important;
    height: $menu-top;
    line-height: $menu-top;
    padding: 0 rpx(12);
    font-size: rpx(17);
    font-weight: 600;
    // width: 1.6rem;
    font-family: Mircosoft Yahei;
    color: #333;
    .logo-istar {
      color: $primary;
    }
  }

  .ys-query_avatar {
    position: absolute;
    right: 5px;
  }

  .menu {
    height: $menu-top;
    border-right: rpx(0);
    border-bottom: rpx(0);

    .el-menu-item {
      padding: 0 rpx(30) 0 rpx(25) !important;
      height: rpx(55);
      line-height: $menu-top;
      i.menu-item-logo {
        font-size: rpx(24);
        margin-right: rpx(5);
      }

      b {
        font-size: rpx(15) !important;
        line-height: rpx(24) !important;
        font-weight: bold;
      }
    }

    .el-menu-item:hover {
      i.menu-item-logo {
        margin-right: rpx(5);
        color: $menu-active;
      }

      b {
        color: $menu-active;
      }
    }

    .el-menu-item.is-active {
      border-bottom: rpx(2) solid $menu-active;

      i.menu-item-logo {
        margin-right: rpx(5);
        color: $menu-active;
      }

      b {
        color: $menu-active;
      }
    }
  }
}
.nav-left {
  position: fixed;
  top: $menu-top;
  left: 0px;
  width: $menu-left;
  bottom: 0px;
  overflow-x: hidden;
  overflow-y: auto;
  z-index: 100;
  border-right: 1px solid #ebeef5;
  box-shadow: 0 10px 10px #00000014;

  &.menu {
    width: $menu-left;
    .el-menu-item {
      padding: 0 rpx(10) !important;
      height: rpx(33);
      min-width: auto;
      line-height: rpx(32);
      margin: rpx(10);
      border-radius: 5px;
      i.menu-item-logo {
        font-size: rpx(24);
        margin-right: rpx(5);
      }

      b {
        font-size: rpx(12) !important;
        line-height: rpx(24) !important;
      }
    }

    .el-menu-item:hover {
      background-color: $menu-bg-active;
    }

    .el-menu-item.is-active {
      background-color: $menu-active;
      color: rgba($white, 0.9);
      &:after {
        content: "";
        width: 3px;
        height: 50%;
        position: absolute;
        top: 25%;
        right: -8px;
        background-color: $primary;
        border-top-left-radius: 8px;
        border-bottom-left-radius: 8px;
      }
    }

    .el-submenu {
      padding: rpx(0) !important;
      width: $menu-left;

      /deep/ .el-submenu__title {
        height: rpx(33);
        line-height: rpx(33);
        color: #333;

        i.menu-item-logo {
          font-size: rpx(24);
          margin-right: rpx(5);
        }

        b {
          font-size: rpx(12) !important;
          line-height: rpx(24) !important;
        }
      }
    }
  }
}
</style>