vue-element-admin 和 element ui

653 阅读7分钟

如何修改vue-element-admin 导航logo显示,设置文本和logo

logo渲染位置在 src\layout\components\Sidebar\index.vue 中

由showLogo控制,默认是false,

<logo v-if="showLogo" :collapse="isCollapse"/>

在 setting.js 中定义了该变量。在src根目录下。修改为true即可

sidebarLogo: true

修改login和文本的方式

在 src\layout\components\Sidebar\Logo.vue,在下面的title和logo中改

data() {
    return {
      title: 'Vue Element Admin',
      logo: 'https://wpimg.wallstcn.com/69a1c46c-eb1c-4b46-8bd4-e9e686ef5251.png'
    }

关闭右侧栏设置按钮

右侧按钮是在 src\layout\components\Settings\index.vue

被引用在 src\layout\index.vue

<template>
  <!-- 注释掉下面的 -->
  <right-panel v-if="showSettings">
    <settings />
  </right-panel>
</template>

vue-element-admin 实现动态路由

先看下权限路由,在 router.js 中 title: '用户管理', roles: ['超级管理员'] 表示可访问角色

/**
 * asyncRoutes 异步路由也叫做权限路由,我们需要些核心判断逻辑
 */
export const asyncRoutes = [
  {
    path: '/system',
    component: Layout,
    redirect: 'system-user',
    name: 'system',
    meta: { title: '系统管理', icon: 'excel' },
    children: [
      {
        path: 'system-user',
        component: () => import('@/views/sysuser/index.vue'),
        name: 'system-user',
        meta: { title: '用户管理', roles: ['超级管理员'] }
      },
      {
        path: 'system-data',
        component: () => import('@/views/system/index.vue'),
        name: 'system-data',
        meta: { title: '系统设置', roles: ['超级管理员'] }
      }
    ]
  }
  // 404 page must be placed at the end !!!
  // { path: '*', redirect: '/404', hidden: true }
]
  1. 请求接口获取登录用户信息,主要是角色---在src/store/modules/user.js 的48行去getInfo
// 请求接口
  getInfo({ commit, state }) {
    return new Promise((resolve, reject) => {
      getInfo().then(response => {
        let rolesNew = [] // 设置角色
        rolesNew.push(response.data.sysUser.roleName)

        const data = {
          roles: rolesNew, // 赋值
          introduction: 'I am a super administrator',
          // 设置头像
          avatar: 'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
          // 设置名称
          name: response.data.sysUser.nickName
        }

        if (!data) {
          reject('Verification failed, please Login again.')
        }

        const { roles, name, avatar, introduction } = data

        // roles must be a non-empty array
        if (!roles || roles.length <= 0) {
          reject('getInfo: roles must be a non-null array!')
        }
        
        // 设置角色,名字,头像
        commit('SET_ROLES', roles)
        commit('SET_NAME', name)
        commit('SET_AVATAR', avatar)
        commit('SET_INTRODUCTION', introduction)
        resolve(data)
      })
    })
  1. 前面定义的数据,会在 permission.js 文件中判断权限---src\permission.js 13行左右,全局前置路由处
  • 只看将角色传递给权限控制,生成动态路由注释这里,即可
  • 大概内容,就是使用Vuex的dispatch方法触发名为permission/generateRoutes的action,并将roles角色信息传递过去(跳转过去,我们去写逻辑)
router.beforeEach(async(to, from, next) => {
  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // determine whether the user has logged in
  const hasToken = getToken()

  if (hasToken) {
    if (to.path === '/login') {
      // if is logged in, redirect to the home page
      next({ path: '/' })
      NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
    } else {
      // determine whether the user has obtained his permission roles through getInfo
      const hasRoles = store.getters.roles && store.getters.roles.length > 0
      if (hasRoles) {
        next()
      } else {
        try {
          // get user info
          // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
          const { roles } = await store.dispatch('user/getInfo')

          // 将角色传递给权限控制,生成动态路由
          const accessRoutes = await store.dispatch('permission/generateRoutes', roles)

          // dynamically add accessible routes
          router.addRoutes(accessRoutes)

          // hack method to ensure that addRoutes is complete
          // set the replace: true, so the navigation will not leave a history record
          next({ ...to, replace: true })
        } catch (error) {
          // remove token and go to login page to re-login
          await store.dispatch('user/resetToken')
          Message.error(error || 'Has Error')
          next(`/login?redirect=${to.path}`)
          NProgress.done()
        }
      }
    }
  } else {
    /* has no token*/

    if (whiteList.indexOf(to.path) !== -1) {
      // in the free login whitelist, go directly
      next()
    } else {
      // other pages that do not have permission to access are redirected to the login page.
      next(`/login?redirect=${to.path}`)
      NProgress.done()
    }
  }
})
  1. 前面配置了根据角色动态生成路由接下来就是编写逻辑
  • 前面跳转的位置是:src/store/modules/permission.js文件的action方法 49行
const actions = {
  generateRoutes({ commit }, roles) {
    return new Promise(resolve => {
      // 通过过滤和克隆生成访问权限的路由数组
      const accessedRoutes = asyncRoutes.filter(route => {
        const clonedRoute = { ...route } // 克隆路由对象,避免修改原始路由对象
        // 如果路由有子路由
        if (clonedRoute.children) {
          // 过滤子路由,只保留符合角色要求的子路由
          clonedRoute.children = clonedRoute.children.filter(child => {
            return child.meta.roles.some(role => roles.includes(role))
          })
        }

        return clonedRoute.children.length > 0 // 只保留有子路由的父级路由
      })

      // 提交设置路由的 mutation
      commit('SET_ROUTES', accessedRoutes)
      resolve(accessedRoutes)
    })
  }
}

完善一下,发起请求查询用户角色权限,快速配置

const actions = {
    generateRoutes({ commit }, roles) {
        return new Promise(resolve => {
            RoleMenuGenerator(roles.toString()).then(res => {
                // 通过过滤和克隆生成访问权限的路由数组
                // 遍历 asyncRoutes 数组,过滤掉没有权限的子路由
                const cloneRouterFilter = asyncRoutes.map(route => {
                  // 创建路由的克隆
                  let clonedRouter = { ...route }

                  // 过滤子路由,根据用户权限来进行过滤
                  clonedRouter.children = clonedRouter.children.filter(childRoute => {
                    return res.data.some(obj => obj.menuName === childRoute.meta.title)
                  })

                  // 返回克隆后的路由
                  return clonedRouter
                })

                // 过滤掉所有不包含子路由的父路由
                const routerClone = cloneRouterFilter.filter(item => item.children.length !== 0)
                
                // 將第一个元素的path路径修改为 '/'
                if (routerClone.length !== 0) {  
                    routerClone[0].path = '/'  
                }
                
                // 提交设置路由的 mutation
                commit('SET_ROUTES', routerClone)

                // 作为 Promise 的值返回访问权限的路由数组
                resolve(routerClone)
              })

完善跳转到不存在的路由跳转

在 src/permission.js

router.beforeEach(async(to, from, next) => {

  // start progress bar
  NProgress.start()

  // set page title
  document.title = getPageTitle(to.meta.title)

  // determine whether the user has logged in
  const hasToken = getToken()
if (hasToken) {
    if (to.path === '/login') {
        // if is logged in, redirect to the home page
        next({ path: '/' })
        NProgress.done() // hack: https://github.com/PanJiaChen/vue-element-admin/pull/2939
    } else {
        // determine whether the user has obtained his permission roles through getInfo
        const hasRoles = store.getters.roles && store.getters.roles.length > 0
    if (hasRoles) {
    // to.path
    console.log(to)
    // 如果访问的当前路径,并不由存在,则跳转到 '/'
    if (to.matched.length === 0) {
      next({ path: '/' })
    }
    next()
    } else {
        try {
            // get user info
            // note: roles must be a object array! such as: ['admin'] or ,['developer','editor']
            const { roles } = await store.dispatch('user/getInfo')

            // 将角色传递给权限控制,生成动态路由
            const accessRoutes = await store.dispatch('permission/generateRoutes', roles)
            if (accessRoutes.length === 0) {
            next({ path: '/401' })
            return
        }

按钮级权限控制,利用vue-element-admin 配置好的,直接使用,根据角色控制

<span slot="footer" class="dialog-footer">
        <el-button @click="dialogVisible = false">取 消</el-button>
        <el-button v-permission="['超级管理员']" type="primary" @click="updateFlush">确 定</el-button>
</span>
import permission from '@/directive/permission'
export default {
  directives: { permission }
}

修改打开窗口右键的文字

在 src/layout/components/TagsView/index.vue

<ul v-show="visible" :style="{left:left+'px',top:top+'px'}" class="contextmenu">
  <li @click="refreshSelectedTag(selectedTag)">刷新</li>
  <li v-if="!isAffix(selectedTag)" @click="closeSelectedTag(selectedTag)">关闭</li>
  <li @click="closeOthersTags">关闭其他</li>
  <li @click="closeAllTags(selectedTag)">关闭所有</li>
</ul>

退出时关闭所有标签页

在src/layout/components/Navbar.vue调用 src/store/modules/tagsView.js 的方法进行关闭,也可以不关闭直接跳转首页即可

async logout() {
  // 关闭所有标签
  await this.$store.dispatch('tagsView/delAllViews');
  // 跳转首页
  this.$router.push('/')
  await this.$store.dispatch('user/logout')
  this.$router.push(`/login?redirect=${this.$route.fullPath}`)
}

在任何页面关闭指定页面

// 根据过滤条件,找出对应的标签页面
const view = this.$store.state.tagsView.visitedViews.filter(view => view.meta.title === this.name)[0]
// 关闭该页面
this.$store.dispatch('tagsView/delView', view);

出现认证失败或者登录过期,无法跳转到登录页面解决方案

在 utils/request.js 中

response => {
    const res = response.data
    // 判断结果是不是不等于 200 并且不等于 401
    if (res.code !== 200 && res.code !== 401) {
      Message({
        message: res.msg || 'Error',
        type: 'error',
        duration: 5 * 1000
      })

      // 下面是一些 授权码,如果 等于 401 就让跳转登录页面重新登录
      if (res.code === 50008 || res.code === 50012 || res.code === 50014 || res.code === 401) {
        // to re-login
        MessageBox.alert('登录过期,请重新登录', '提示', {
          confirmButtonText: '确定',
          type: 'warning'
        }).then(() => {
        // 删除 cookie,并且跳转
          store.dispatch('user/resetToken').then(() => {
            location.reload()
          })
        })
      }
      return Promise.reject(new Error(res.message || 'Error'))
    } else {
      return res
    }
  },

配置带有指定参数的面包屑不显示

我们先在路由中配置 breadcrumb 为 false

再打开 src/layout/components/TagsView/index.vue

{
path: '/index2',
component: Layout,
redirect: '/index2',
children: [
  {
    path: 'index2',
    component: () => import('@/views/index2/index'),
    name: 'index2',
    meta: { title: 'index2', breadcrumb: false},
    props: true
  }
]
},

后面加上过滤 breadcrumb 为 false 即可

  computed: {
    visitedViews() {
      return this.$store.state.tagsView.visitedViews.filter(view => view.meta.breadcrumb !== false)
    }
  },

表格表头加深颜色

:header-cell-style="headClass"

在 methods 中写

headClass(){
  return 'background:#F8F8F9'
},

Tree 型树状展开 / 关闭所有节点 / 全选 / 全不选

// props,标签名称定义
defaultProps: {
    children: 'subMenu', // 子集标签
    label: 'parentMenu' // 标签文本
}


<el-checkbox-group v-model="checkList">
    <el-checkbox label="展开/折叠" @change="toggle"></el-checkbox>
    <el-checkbox label="全选全不选" @change="checkAll"></el-checkbox>
</el-checkbox-group>

<!--:default-expanded-keys 默认选中的节点-->
<el-tree :data="data" show-checkbox node-key="id" :default-expanded-keys="openedNodes" :props="defaultProps"
       ref="selectTree"
></el-tree>

展开 / 不展开

// 切换所有节点的展开或折叠状态
toggle(val) {
  if (val) {
    this.isExpanded = true
    this.toggleAllNode(this.$refs.selectTree.store.root)
  } else {
    this.isExpanded = false
    this.toggleAllNode(this.$refs.selectTree.store.root)
  }
},

// 递归方法,切换节点的展开或折叠状态
toggleAllNode(node) {
  const children = node.childNodes // 获取当前节点的所有子节点
  if (children) {
    children.forEach(child => {
      this.toggleAllNode(child) // 递归切换每个子节点的展开或折叠状态
    })
  }
  node.expanded = this.isExpanded // 设置当前节点的展开或折叠状态
},

全选 / 不全选

// 选中或取消选中所有节点
checkAll(val) {
  const root = this.$refs.selectTree.store.root // 获取树形控件的根节点
  this.traverseNode(root, node => {
    node.setChecked(val, true) // 设置节点选中状态并触发事件
  })
},

// 递归方法,遍历每个节点
traverseNode(node, callback) {
  callback(node) // 执行自定义回调函数
  if (node.childNodes) {
    node.childNodes.forEach(child => {
      this.traverseNode(child, callback) // 递归遍历所有子节点
    })
  }
}

获取选中

// 获取所有已选节点的数据
getSelectedNodes() {
    const checkedNodes = this.$refs.selectTree.getCheckedNodes() // 获取已选节点的数组
    // checkedNodes节点的数组
    const selectedNodes = [] // 创建一个空数组
    checkedNodes.forEach(node => {
        selectedNodes.push(node) // 将每个已选节点的数据添加到数组
    })
    const subMenuIds = []
    // 遍历所有节点
    selectedNodes.forEach(item => {
        // 如果存在字节点继续遍历
        if (item.subMenu) {
          item.subMenu.forEach(subItem => {
            // 获取id
            subMenuIds.push(subItem.id)
          })
        }else {
          // 否则就是子节点,获取id
          subMenuIds.push(item.id)
        }
    })
}

传入数组,默认选中

  • :default-checked-keys="checkDefault" 绑定 el-tree

发送请求

post

export function addUserAndPermission(data) {
  return request.post('/roles', qs.stringify(data))
}

put

export function modifyStatus(data) {
  return request.put('/roles/modify-status', qs.stringify(data))
}

del

export function delRoleById(data) {
  return request.delete(`/roles/${data}`)
}

vue-element-admin npm i 报错

1:先在package.json中将 “tui-editor”: "^1.3.3"删除掉。 2:最后自己安装这个包

npm install tui-editor@1.3.3 --registry=https://registry.npm.taobao.org

npm i 校验异常

删除package-lock.json

element ui 按需引入

依赖

npm install babel-plugin-component -S

创建 element ui文件夹,和 js文件

引入所需组件

import Vue from 'vue'
import { Card } from 'element-ui'

Vue.use(Card)

在 main.js 中引入element ui下的js文件

在 babel.config.js文件中,覆盖以下内容

module.exports = {
  presets: [['@babel/preset-env', { modules: false }]],
  plugins: [
    [
      'component',
      {
        libraryName: 'element-ui',
        styleLibraryName: 'theme-chalk'
      }
    ]
  ]
}

实现打包后可以直接打开,不报跨域问题

module.exports = {
  publicPath: './'
}

element ui 表单校验,传入数组,动态方式

  • value:绑定到from
  • :prop="'list.'+index+'.useRushPith'" 必须绑定这样的
  • :rules="[{validator: checkRushPith, trigger: 'blur'}] 这里写规则
<el-form ref="form" :model="value" inline v-loading="loading">
  <el-row type="flex" justify="center" v-for="(item, index) in value.list" :key="index">
      <el-form-item label="请输入芯数" :prop="'list.'+index+'.useRushPith'"
                    :rules="[{validator: checkRushPith, trigger: 'blur'}]">
        <el-input v-model="item.useRushPith"/>
      </el-form-item>
  </el-row>
</el-form>
</template>

<script>
import {getAccessLinksList, InspectRush} from "@/api/access";

export default {
  name: "AddRushPith",
  data() {
    return {
      checkRushPith: async (index, value, callback) => {
      // 拿到数据
        const element = this.value.list[index.field.split('.')[1]]
        const some = value.split('-').some(number => +number > +element.rushPith)
        if (some) {
          callback(new Error('输入值大于最大芯数'))
          return
        }
      },
      linkId: '',
      value: {
        list: []
      },
      loading: false
    }
  },
  methods: {
    async submit() {
      console.log("进入校验")
      await this.$refs.form.validate()
      console.log("校验通过")
    }
  }
}
</script>

vue 前端路由创建

遇到问题:报错 Error: Cannot find module './[object Object]' at webpackContextResolve (eval at ./src/views sync recursive ^./.* (0.js:2417:1), \<anonymous>:186:11) at webpackContext (eval at ./src/views sync recursive ^\.\/.* (0.js:2417:1), <anonymous>:181:11) at eval (permission.js:190:1)

原因是创建对象时,需要 component: 'ParentView', 而不是 component: ParentView, 在若依代码中后面还需要转换为对象。

// 生成路由
GenerateRoutes({ commit }) {
  return new Promise(resolve => {
    // 向后端请求路由数据
    getRouters().then(async res => {
      // 删除一个(根据需求来)
      res.data.splice(3, 1)
      // 调用接口
      const { data } = await listTunnelAll()
      // 创建对象
      const any = RouterJs(data)
      any.forEach(a => res.data.push(a))

      const sdata = JSON.parse(JSON.stringify(res.data))
      const rdata = JSON.parse(JSON.stringify(res.data))
      const sidebarRoutes = filterAsyncRouter(sdata)
      // console.log(sidebarRoutes)
      const rewriteRoutes = filterAsyncRouter(rdata, false, true)
      const asyncRoutes = filterDynamicRoutes(dynamicRoutes)
      rewriteRoutes.push({ path: '*', redirect: '/404', hidden: true })

创建路由对象

// 路由对象
export const RouterJs = data => {
  return data.map(type => ({
    alwaysShow: true, // 总是显示此层级
    children: type.tunnels.map(tunnel => {
      return ({
        alwaysShow: true, // 总是显示此层级
        children: tunnel.tunnelLevels.map((level, index) => {
          const path = level.component.split('/') // 分割组件路径
          return {
            component: level.component, // 动态加载组件
            hidden: false, // 是否隐藏路由,false表示不隐藏
            meta: {
              icon: '#', // 图标,实际使用时替换为实际的图标
              link: null, // 链接,默认为null
              noCache: false, // 是否缓存,false表示缓存
              title: level.menuName // 菜单名称
            },
            name: level.path.charAt(0).toLocaleUpperCase() + level.path.slice(1) + index + tunnel.id + type.id, // 动态生成路由名称
            path: path[0] + '/' + path[1], // 动态生成路由路径
            query: `{"id":${type.id}}` // 查询参数
          }
        }),
        component: 'ParentView', // 父级组件
        hidden: false, // 是否隐藏路由,false表示不隐藏
        meta: {
          icon: '#', // 图标,实际使用时替换为实际的图标
          link: null, // 链接,默认为null
          noCache: false, // 是否缓存,false表示缓存
          title: tunnel.name // 菜单名称
        },
        name: `CableIn${tunnel.id}`, // 动态生成路由名称
        path: `cableIn${tunnel.id}`, // 动态生成路由路径
        redirect: 'noRedirect' // 重定向路径
      })
    }),
    component: 'Layout', // 布局组件
    hidden: false, // 是否隐藏路由,false表示不隐藏
    meta: {
      icon: '#', // 图标,实际使用时替换为实际的图标
      link: null, // 链接,默认为null
      noCache: false, // 是否缓存,false表示缓存
      title: type.level // 菜单名称
    },
    name: `Cable${type.id}`, // 动态生成路由名称
    path: `/cable${type.id}`, // 动态生成路由路径
    redirect: 'noRedirect' // 重定向路径
  }))
}