若依前后端分离版学习笔记(十八)——页面权限,页签缓存以及图标,字典,参数的使用

一、页面权限

封装指令权限,能简单快速实现按钮级别的权限判断

1.1 hasRole

角色权限处理

javascript 复制代码
 /**
 * v-hasRole 角色权限处理
 * Copyright (c) 2019 ruoyi
 */
import useUserStore from '@/store/modules/user'

export default {
  mounted(el, binding, vnode) {
    const { value } = binding
    const super_admin = "admin"
    // 从useUserStore中获取当前用户的角色列表
    const roles = useUserStore().roles

    // 如果传入的指令值存在,value为非空数组
    if (value && value instanceof Array && value.length > 0) {
      const roleFlag = value
      // 验证用户是否具有指定角色之一,或者是否为超级管理员 admin
      const hasRole = roles.some(role => {
        return super_admin === role || roleFlag.includes(role)
      })

      // 如果不具有相应角色,则从DOM中移除该元素
      if (!hasRole) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`请设置角色权限标签值`)
    }
  }
}

使用示例

javascript 复制代码
<el-button v-hasRole="['admin', 'editor']">编辑</el-button>

1.2 hasPermi

权限标识处理

javascript 复制代码
import useUserStore from '@/store/modules/user'

export default {
  mounted(el, binding, vnode) {
    const { value } = binding
    const all_permission = "*:*:*"
    // 从useUserStore获取权限标识列表
    const permissions = useUserStore().permissions
    // 检查传入的指令值是否为非空数组
    if (value && value instanceof Array && value.length > 0) {
      const permissionFlag = value
      // 判断用户权限是否有全部权限或具有指定权限
      const hasPermissions = permissions.some(permission => {
        return all_permission === permission || permissionFlag.includes(permission)
      })

      // 如果不具有指定权限,则移除DOM元素
      if (!hasPermissions) {
        el.parentNode && el.parentNode.removeChild(el)
      }
    } else {
      throw new Error(`请设置操作权限标签值`)
    }
  }
}

使用示例

javascript 复制代码
<el-button v-hasPermi="['system:user:add', 'system:user:edit']">新增用户</el-button>

1.3 使用示例

示例代码

javascript 复制代码
<!-- 父组件 -->
<template>

  <!-- hasRole 使用示例 -->
  <div style="margin: 20px; padding: 20px; border: 1px solid #ccc;">
    <h3>角色权限控制示例 (v-hasRole)</h3>
    <el-button v-hasRole="['admin']" type="primary">仅管理员可见</el-button>
    // hasRole.js中设置了admin所有可见,所以这里v-hasRole="['common']" 管理员也是有权限的
    <el-button v-hasRole="['common']" type="success">编辑者和管理员可见</el-button>
  </div>

  <!-- hasPermi 使用示例 -->
  <div style="margin: 20px; padding: 20px; border: 1px solid #ccc;">
    <h3>操作权限控制示例 (v-hasPermi)</h3>
    <el-button v-hasPermi="['system:user:add']" type="primary">新增用户</el-button>
    <el-button v-hasPermi="['system:user:edit']" type="success">编辑用户</el-button>
    <el-button v-hasPermi="['system:user:remove']" type="danger">删除用户</el-button>
    <el-button v-hasPermi="['system:user:add', 'system:user:edit']" type="warning">新增或编辑用户</el-button>
  </div>

  <!-- 函数式权限检查示例 -->
  <div style="margin: 20px; padding: 20px; border: 1px solid #ccc;">
    <h3>函数式权限检查</h3>
    <div v-if="checkRole(['admin'])">
      <p>这是通过函数检查显示的内容 - 仅管理员可见</p>
    </div>
    <div v-if="checkPermi(['system:config:edit'])">
      <p>这是通过函数检查显示的内容 - 编辑者和管理员可见</p>
    </div>
  </div>
</template>

<script setup>
import { checkPermi, checkRole } from '@/utils/permission'

</script>

admin账号查看

ry(普通角色)账号查看

这里注意要给普通角色添加新增的目录权限

v-if 是 Vue 的条件渲染指令,它根据表达式的真假值来决定是否渲染某个元素或组件;当 v-if 的值为 true 时,元素会被渲染到 DOM 中;

当 v-if 的值为 false 时,元素不会被渲染到 DOM 中(完全不存在于 DOM 树中)

二、页签缓存

页签缓存是一个重要的用户体验优化功能。在ruoyi中,它允许用户在切换不同页面时保持页面状态,避免重复加载和数据丢失。在ruoyi中,页签缓存主要通过Vue的组件实现

javascript 复制代码
<template>
  <section class="app-main">
    <router-view v-slot="{ Component, route }">
      <transition name="fade-transform" mode="out-in">
        <keep-alive :include="tagsViewStore.cachedViews">
          <component v-if="!route.meta.link" :is="Component" :key="route.path"/>
        </keep-alive>
      </transition>
    </router-view>
  </section>
</template>
  • keep-alive是Vue内置组件,用于缓存动态组件
  • :include="tagsViewStore.cachedViews"指定了需要缓存的组件列表
  • 只有在cachedViews数组中的组件才会被缓存

使用keep-alive进行组件缓存时,必须确保路由配置和组件的name属性保持一致,否则会导致缓存失效或者出现其他问题。在Vue中,keep-alive组件通过include属性来决定哪些组件需要被缓存。默认情况下,keep-alive会优先匹配组件的name属性。如果路由配置中的name与组件定义的name不一致,就会导致缓存机制失效。

在系统管理-菜单管理中可以配置菜单页签是否缓存,默认为缓存。

缓存的具体管理在src→store→modules→tagsViews.js中具体实现

javascript 复制代码
addCachedView(view) {
    // 如果该页面已在缓存列表中,则不再重复添加
    if (this.cachedViews.includes(views.name)) return
    // 如果该页面的路由配置中meta.noCache不为true,则将路由的name添加到缓存列表
    if (!view.meta.noCache) {
        this.cachedViews.push(view.name)    
    }
}

实际使用场景

1.用户首次访问/system/user页面,系统会加载用户管理组件

2.由于该路由未设置meta.noCache=true,所以该页面组件会被添加到cachedViews列表中

3.当用户切换到其他页面(如角色管理)再切换回来时,由于组件已在cachedViews列表中,keep-alive会直接从缓存中恢复组件状态,而不会重新创建组件

4.这样用户在用户管理页面中的操作状态(如表格的分页、筛选条件等)会被保留

三、使用图标

3.1 图标

icon图标放在src→assets→icons下

3.2 全局SvgIcon组件

src→components→SvgIcon→index.vue

javascript 复制代码
<template>
  <svg :class="svgClass" aria-hidden="true">
    <use :xlink:href="iconName" :fill="color" />
  </svg>
</template>

<script>
export default defineComponent({
  // 定义从父组件接收的值
  props: {
    iconClass: {
      type: String,
      required: true
    },
    className: {
      type: String,
      default: ''
    },
    color: {
      type: String,
      default: ''
    },
  },
  setup(props) {
    // 要在模板中使用的响应式数据和方法
    return {
      iconName: computed(() => `#icon-${props.iconClass}`),
      svgClass: computed(() => {
        if (props.className) {
          return `svg-icon ${props.className}`
        }
        return 'svg-icon'
      })
    }
  }
})
</script>

// 定义组件样式
<style scope lang="scss">
.sub-el-icon,
.nav-icon {
  display: inline-block;
  font-size: 15px;
  margin-right: 12px;
  position: relative;
}

.svg-icon {
  width: 1em;
  height: 1em;
  position: relative;
  fill: currentColor;
  vertical-align: -2px;
}
</style>

使用方式

javascript 复制代码
<!-- icon-class 为 icon 的名字; class-name 为 icon 自定义 class-->
javascript 复制代码
<!-- 基本用法 -->
<svg-icon icon-class="user" />

<!-- 带颜色 -->
<svg-icon icon-class="edit" color="#409EFF" />

<!-- 带额外CSS类 -->
<svg-icon icon-class="dashboard" class-name="custom-class" />

实际示例

javascript 复制代码
<script setup>

</script>

<template>
 <div class="app-container">
   msg
 </div>
  <svg-icon icon-class="user" color = "#409EFF" class-name="user-class"/>
  <svg-icon icon-class="password" class-name="password-class"/>
</template>

<style scoped lang="scss">
.password-class{
  font-size: 200px;
  fill: red;
}
</style>

显示效果

3.3 新增图标

提示

如果是从iconfont下载的图标,记得使用如 Sketch 等工具规范一下图标的大小问题,不然可能会造成项目中的图标大小尺寸不统一的问题。 项目中使用的图标都是 128*128 大小规格的。

将新图标放到src→assets→icons下,并使用即可,如:

javscript 复制代码
<svg-icon icon-class="newIcon" class-name="newIcon-class"/>

四、使用字典

数据字典系统是一个重要的功能模块,用于管理系统中的各种枚举数据,如状态、性别、类型等。它通过前后端配合,实现了数据字典的统一管理和使用。

流程:

1.初始化:通过 useDict 函数加载所需字典数据

2.缓存:将获取的字典数据存储在 dict.js 存储模块中,避免重复请求

3.显示:通过 DictTag 组件根据字典值显示对应标签

4.更新:当字典数据发生变化时,可以刷新缓存

4.1 核心组件和实现

4.1.1 DictTag组件

src→components→DictTag→index.vue是项目中用于显示字典标签的核心组件,它可以根据字典值显示对应的标签文本,并支持不同的显示样式。

javascript 复制代码
<template>
  <div>
    // 遍历所有字典选项
    <template v-for="(item, index) in options">
      // 通过 values.includes(item.value) 判断当前值是否匹配字典项
      <template v-if="values.includes(item.value)">
        <span
          // 根据是否有自定义样式类和类型,决定使用普通 span 标签还是 el-tag 组件显示
          v-if="(item.elTagType == 'default' || item.elTagType == '') && (item.elTagClass == '' || item.elTagClass == null)"
          :key="item.value"
          :index="index"
          :class="item.elTagClass"
        >{{ item.label + " " }}</span>
        <el-tag
          v-else
          :disable-transitions="true"
          :key="item.value + ''"
          :index="index"
          :type="item.elTagType"
          :class="item.elTagClass"
        >{{ item.label + " " }}</el-tag>
      </template>
    </template>
  </div>
</template>

<script setup>
// 记录未匹配的项
const unmatchArray = ref([])

// 接收属性
const props = defineProps({
  // 数据
  options: {
    type: Array,
    default: null,
  },
  // 当前的值
  value: [Number, String, Array],
  // 当未找到匹配的数据时,显示value
  showValue: {
    type: Boolean,
    default: true,
  },
  // 分隔符,默认为逗号
  separator: {
    type: String,
    default: ",",
  }
})

// 计算属性 values 用于处理传入的值,将其统一转换为字符串数组
const values = computed(() => {
  if (props.value === null || typeof props.value === 'undefined' || props.value === '') return []
  return Array.isArray(props.value) ? props.value.map(item => '' + item) : String(props.value).split(props.separator)
})

const unmatch = computed(() => {
  unmatchArray.value = []
  // 没有value不显示
  if (props.value === null || typeof props.value === 'undefined' || props.value === '' || !Array.isArray(props.options) || props.options.length === 0) return false
  // 传入值为数组
  let unmatch = false // 添加一个标志来判断是否有未匹配项
  values.value.forEach(item => {
    if (!props.options.some(v => v.value === item)) {
      unmatchArray.value.push(item)
      unmatch = true // 如果有未匹配项,将标志设置为true
    }
  })
  return unmatch // 返回标志的值
})

function handleArray(array) {
  if (array.length === 0) return ""
  return array.reduce((pre, cur) => {
    return pre + " " + cur
  })
}
</script>

<style scoped>
.el-tag + .el-tag {
  margin-left: 10px;
}
</style>

4.1.2 字典获取工具

src→utils→dict.js 用于获取字段数据的工具函数

javascript 复制代码
import useDictStore from '@/store/modules/dict'
import { getDicts } from '@/api/system/dict/data'

/**
 * 获取字典数据
 */
 // 接收多个字典类型参数
export function useDict(...args) {
  const res = ref({})
  return (() => {
    // 循环处理每个参数
    args.forEach((dictType, index) => {
      res.value[dictType] = []
      // 先尝试从本地获取
      const dicts = useDictStore().getDict(dictType)
      if (dicts) {
        res.value[dictType] = dicts
      } else {
        // 本地没有则调用api获取
        getDicts(dictType).then(resp => {
          // 将获取到的数据转换为统一格式(包含 label、value、elTagType、elTagClass)
          res.value[dictType] = resp.data.map(p => ({ label: p.dictLabel, value: p.dictValue, elTagType: p.listClass, elTagClass: p.cssClass }))
          useDictStore().setDict(dictType, res.value[dictType])
        })
      }
    })
    return toRefs(res.value)
  })()
}

字典api接口

src→api→system→dict→data.js

javascript 复制代码
// 根据字典类型查询字典数据信息
export function getDicts(dictType) {
  return request({
    url: '/system/dict/data/type/' + dictType,
    method: 'get'
  })
}

4.1.3 字典存储

src→store→modules→dict.js 存储模块管理字典数据,避免重复请求

javascript 复制代码
const useDictStore = defineStore(
  'dict',
  {
    state: () => ({
      dict: new Array()
    }),
    actions: {
      // 获取字典
      getDict(_key) {
        if (_key == null && _key == "") {
          return null
        }
        try {
          for (let i = 0; i < this.dict.length; i++) {
            if (this.dict[i].key == _key) {
              return this.dict[i].value
            }
          }
        } catch (e) {
          return null
        }
      },
      // 设置字典
      setDict(_key, value) {
        if (_key !== null && _key !== "") {
          this.dict.push({
            key: _key,
            value: value
          })
        }
      },
      // 删除字典
      removeDict(_key) {
        var bln = false
        try {
          for (let i = 0; i < this.dict.length; i++) {
            if (this.dict[i].key == _key) {
              this.dict.splice(i, 1)
              return true
            }
          }
        } catch (e) {
          bln = false
        }
        return bln
      },
      // 清空字典
      cleanDict() {
        this.dict = new Array()
      },
      // 初始字典
      initDict() {
      }
    }
  })

export default useDictStore

4.2 实际使用示例

在用户管理模块中的使用

在src/views/system/user/index.vue中

javascript 复制代码
// 获取两个字典 sys_normal_disable: 通用状态字典(正常/停用),sys_user_sex: 用户性别字典
const { sys_normal_disable, sys_user_sex } = proxy.useDict("sys_normal_disable", "sys_user_sex")

模板中使用

javascript 复制代码
<!-- 状态选择 -->
<el-select v-model="queryParams.status" placeholder="用户状态" clearable style="width: 240px">
  <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>

<!-- 性别选择 -->
<el-select v-model="form.sex" placeholder="请选择">
  <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>

<!-- 状态显示 -->
<el-table-column label="状态" align="center" key="status" v-if="columns[5].visible">
  <template #default="scope">
    <el-switch
      v-model="scope.row.status"
      active-value="0"
      inactive-value="1"
      @change="handleStatusChange(scope.row)"
    ></el-switch>
  </template>
</el-table-column>

在字典数据管理中的使用

在 src/views/system/dict/data.vue 中:

javascript 复制代码
<el-table-column label="状态" align="center" prop="status">
  <template #default="scope">
    // 这里使用了DicTag组件显示状态,通过:options传入字典选项,通过:value传入当前值
    <dict-tag :options="sys_normal_disable" :value="scope.row.status" />
  </template>
</el-table-column>

字典更新机制

在 src/views/system/dict/data.vue中

javascript 复制代码
/** 提交按钮 */
function submitForm() {
  proxy.$refs["dataRef"].validate(valid => {
    if (valid) {
      if (form.value.dictCode != undefined) {
        updateData(form.value).then(response => {
          // 更新字典后清除缓存
          useDictStore().removeDict(queryParams.value.dictType)
          proxy.$modal.msgSuccess("修改成功")
          open.value = false
          getList()
        })
      } else {
        addData(form.value).then(response => {
          // 新增字典后清除缓存
          useDictStore().removeDict(queryParams.value.dictType)
          proxy.$modal.msgSuccess("新增成功")
          open.value = false
          getList()
        })
      }
    }
  })
}

点击刷新缓存按钮,调用api /system/dict/data 刷新缓存

五、使用参数

参数设置是提供开发人员、实施人员的动态系统配置参数,不需要去频繁修改后台配置文件,也无需重启服务器即可生效。

5.1 参数修改流程

1.查看参数列表

用户在参数配置页面查看所有系统参数,包括参数名称、建名、键值等信息。

2.修改参数

2.1 点击修改按钮

2.2 修改参数键值

2.3 确定提交修改

3.刷新缓存

5.2 实现

api接口 src/api/system/config.js

  • listConfig:查询参数列表
  • getConfig:查询单个参数详情
  • addConfig:新增参数
  • updateConfig:修改参数
  • delConfig:删除参数
  • refreshCache:刷新参数缓存
    src/views/system/config/index.vue 主要修改逻辑如下
javascript 复制代码
/** 提交按钮 */
function submitForm() {
  proxy.$refs["configRef"].validate(valid => {
    if (valid) {
      if (form.value.configId != undefined) {
        updateConfig(form.value).then(response => {
          proxy.$modal.msgSuccess("修改成功")
          open.value = false
          getList()
        })
      } else {
        addConfig(form.value).then(response => {
          proxy.$modal.msgSuccess("新增成功")
          open.value = false
          getList()
        })
      }
    }
  })
}

当用户修改参数并提交时,会调用updateConfig API接口将更改保存到数据库。

调用refreshCache接口来清除后端缓存,确保修改的参数能够立即生效:

javascript 复制代码
/** 刷新缓存按钮操作 */
function handleRefreshCache() {
  refreshCache().then(() => {
    proxy.$modal.msgSuccess("刷新缓存成功")
  })
}
5.3 参数使用
在用户管理页面 /src/views/system/user/index.vue 中可以看到
onMounted(() => {
  getDeptTree()
  getList()
  proxy.getConfigKey("sys.user.initPassword").then(response => {
    initPassword.value = response.msg
  })
})

在组件挂载时执行,通过调用getConfigKey("sys.user.initPassword")获取系统配置的默认密码参数值,并将其存储在initPassword.value中。

相关推荐
He BianGu2 小时前
【笔记】在WPF中Decorator是什么以及何时优先考虑 Decorator 派生类
笔记·wpf
wan5555cn2 小时前
AI视频生成技术:从想象到现实的视觉革命
人工智能·笔记·深度学习·算法·音视频
琹箐2 小时前
Aupload + vuedraggable实现 上传的文件可以拖拽排序
前端·vue.js
前端 贾公子2 小时前
Vue.js props mutating:反模式如何被视为一种良好实践。
前端·javascript·vue.js
半夏知半秋2 小时前
基于skynet框架业务中的gateway实现分析
服务器·开发语言·后端·学习·gateway
Pluchon11 小时前
硅基计划4.0 算法 字符串
java·数据结构·学习·算法
折翅鵬11 小时前
Android 程序员如何系统学习 MQTT
android·学习
~无忧花开~12 小时前
JavaScript学习笔记(十五):ES6模板字符串使用指南
开发语言·前端·javascript·vue.js·学习·es6·js
哈基鑫13 小时前
深度学习之图像分类笔记
笔记·深度学习·分类