若依(ruoyi)字典管理插件实现思路探究

news2025/1/23 13:00:43

 一个UI表单的构成,避免不了下拉框,多选框等标签,在开发这些标签时,通常会请求后台接口获取字典值进行动态渲染。定制化开发虽然实现简单,但会产生大量重复工作,解决这类问题的思路有哪些?文章对若依字典管理插件实现思路进行了探究,以此来开阔思路。

探究过程如下:

  • 界面设计
  • 数据库设计
  • 开发用例
  • 源码分析

一、界面设计

访问若依管理系统-系统管理-字典管理

界面截图如下:

 

功能提供了字典类型及字典键值的管理

二、数据库设计

SYS_DICT_TYPE

SYS_DICT_DATA

使用到SYS_DICT_TYPE,SYS_DICT_DATA两张表,定义了字典类型,及对应字典键值,两者是一对多的关系,通过dict_type关联。

三、开发用例

以用户性别为例,

功能配置:

字典名称:用户性别
字典类型:sys_user_sex

字典数据

 

页面开发:

  • 引入字典类型
export default {
    name: "User",
    dicts: ['sys_normal_disable', 'sys_user_sex'],
    components: {
      Treeselect
    },
    ......
}
  • 标签使用字典值
          <el-col :span="12">
            <el-form-item label="用户性别">
              <el-select v-model="form.sex" placeholder="请选择性别">
                <el-option v-for="dict in dict.type.sys_user_sex" :key="dict.value" :label="dict.label"
                  :value="dict.value"></el-option>
              </el-select>
            </el-form-item>
          </el-col>

实现效果:

 可以发现,通过灵活的功能配置,vue页面只要引入对应的字典类型,就可以使用字典数据,完成标签的动态渲染,接下来对源码进行分析,探究其实现过程。

四、源码分析

在开发用例中,发现vue自定义属性:dicts: ['sys_user_sex'],申明了vue实例需要引入的字典类型,那问题来了,vue实例是怎么通过这个自定义属性获取对应字典信息呢?

为了回答这个问题,需要先了解一下main.js的使用,参考文章vue项目中main.js使用方法详解,

文中提到“main.js是我们的入口文件,主要作用是初始化vue实例,并引入所需要的插件”,根据提示在main.js中找到与字典插件相关的所有代码段:

import { getDicts } from "@/api/system/dict/data";

// 字典标签组件
import DictTag from '@/components/DictTag'

// 字典数据组件
import DictData from '@/components/DictData'


// 全局方法挂载
Vue.prototype.getDicts = getDicts


// 全局组件挂载
Vue.component('DictTag', DictTag)


DictData.install()

按照这些插件加载的先后顺序进行分析

  • import { getDicts } from "@/api/system/dict/data":一个接口方法,根据字典类型查询字典数据信息。
    // 根据字典类型查询字典数据信息
    export function getDicts(dictType) {
      return request({
        url: '/system/dict/data/type/' + dictType,
        method: 'get'
      })
    }
  • import DictTag from '@/components/DictTag':字典标签组件,根据传入标签值,回显其对应的标签中文名,常用于数据列表中,返回的是标签值,通过DictTag组件可以回显对应的中文描述。
    <template>
      <div>
        <template v-for="(item, index) in options">
          <template v-if="values.includes(item.value)">
            <span
              v-if="item.raw.listClass == 'default' || item.raw.listClass == ''"
              :key="item.value"
              :index="index"
              :class="item.raw.cssClass"
              >{{ item.label }}</span
            >
            <el-tag
              v-else
              :disable-transitions="true"
              :key="item.value"
              :index="index"
              :type="item.raw.listClass == 'primary' ? '' : item.raw.listClass"
              :class="item.raw.cssClass"
            >
              {{ item.label }}
            </el-tag>
          </template>
        </template>
      </div>
    </template>
    
    <script>
    export default {
      name: "DictTag",
      props: {
        options: {
          type: Array,
          default: null,
        },
        value: [Number, String, Array],
      },
      computed: {
        values() {
          if (this.value !== null && typeof this.value !== 'undefined') {
            return Array.isArray(this.value) ? this.value : [String(this.value)];
          } else {
            return [];
          }
        },
      },
    };
    </script>
    <style scoped>
    .el-tag + .el-tag {
      margin-left: 10px;
    }
    </style>
  • import DictData from '@/components/DictData':提供了字典数据组件安装方法。
import Vue from 'vue'
import DataDict from '@/utils/dict'
import { getDicts as getDicts } from '@/api/system/dict/data'

function install() {
  Vue.use(DataDict, {
    metas: {
      '*': {
        labelField: 'dictLabel',
        valueField: 'dictValue',
        request(dictMeta) {
          return getDicts(dictMeta.type).then(res => res.data)
        },
      },
    },
  })
}

export default {
  install,
}

其中 import DataDict from '@/utils/dict'  引入了字典数据组件,是整个实现的核心,是这次分析的重点,涉及以下插件:

其中index.js 是入口程序,实现逻辑如下:

  • 引入了Dict,DictOptions 插件
import Dict from './Dict'
import { mergeOptions } from './DictOptions'
  • 合并配置参数
mergeOptions(options),将DictData的metas 与 DictOptions的metas配置信息进行合并
metas: {
  '*': {
    labelField: 'dictLabel',
    valueField: 'dictValue',
    request(dictMeta) {
      return getDicts(dictMeta.type).then(res => res.data)
    },
  },
}
metas: {
  '*': {
    /**
     * 字典请求,方法签名为function(dictMeta: DictMeta): Promise
     */
    request: (dictMeta) => {
      console.log(`load dict ${dictMeta.type}`)
      return Promise.resolve([])
    },
    /**
     * 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData
     */
    responseConverter,
    labelField: 'label',
    valueField: 'value',
  },
},
  • 通过mixin创建混入对象,将Dict组件 “混合”到各个使用组件本身的选项中。

什么是mixin?可查看博文对vue中mixin的理解。

其中data()方法 “const dict = new Dict()” 负责Dict组件创建,created() 方法中“this.dict.init(this.$options.dicts)”,将vue页面上定义的dicts数组传进去,组装数据,请求后端,获取对应字典数据。

import Dict from './Dict'
import { mergeOptions } from './DictOptions'

export default function(Vue, options) {
  mergeOptions(options)
  Vue.mixin({
    data() {
      if (this.$options === undefined || this.$options.dicts === undefined || this.$options.dicts === null) {
        return {}
      }
      const dict = new Dict()
      dict.owner = this
      return {
        dict
      }
    },
    created() {
      if (!(this.dict instanceof Dict)) {
        return
      }
        
      //  options 如果配置onCreated ,则执行options.onCreated
      options.onCreated && options.onCreated(this.dict)
      this.dict.init(this.$options.dicts).then(() => {
        //options 如果配置onReady ,则执行options.onReady方法
        options.onReady && options.onReady(this.dict)
        this.$nextTick(() => {
          this.$emit('dictReady', this.dict)
          if (this.$options.methods && this.$options.methods.onDictReady instanceof Function) {
            this.$options.methods.onDictReady.call(this, this.dict)
          }
        })
      })
    },
  })
}

对this.dict.init(this.$options.dicts)代码进行分析:其根据传入的options配置信息,生成字典元数据配置信息dictMeta,然后创建回调方法执行数组const ps = [],根据dictMeta加入对应字典类型的loadDict方法,通过Promise.all(ps)发送多个请求并根据请求顺序获取和使用字典数据。

import Vue from 'vue'
import { mergeRecursive } from "@/utils/ruoyi";
import DictMeta from './DictMeta'
import DictData from './DictData'

const DEFAULT_DICT_OPTIONS = {
  types: [],
}

/**
 * @classdesc 字典
 * @property {Object} label 标签对象,内部属性名为字典类型名称
 * @property {Object} dict 字段数组,内部属性名为字典类型名称
 * @property {Array.<DictMeta>} _dictMetas 字典元数据数组
 */
export default class Dict {
  constructor() {
    this.owner = null
    this.label = {}
    this.type = {}
  }

  init(options) {
    if (options instanceof Array) {
      options = { types: options }
    }
    const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options)
    if (opts.types === undefined) {
      throw new Error('need dict types')
    }
    const ps = []
    this._dictMetas = opts.types.map(t => DictMeta.parse(t))
    this._dictMetas.forEach(dictMeta => {
      const type = dictMeta.type
      Vue.set(this.label, type, {})
      Vue.set(this.type, type, [])
      if (dictMeta.lazy) {
        return
      }
      ps.push(loadDict(this, dictMeta))
    })
    return Promise.all(ps)
  }

  /**
   * 重新加载字典
   * @param {String} type 字典类型
   */
  reloadDict(type) {
    const dictMeta = this._dictMetas.find(e => e.type === type)
    if (dictMeta === undefined) {
      return Promise.reject(`the dict meta of ${type} was not found`)
    }
    return loadDict(this, dictMeta)
  }
}

/**
 * 加载字典
 * @param {Dict} dict 字典
 * @param {DictMeta} dictMeta 字典元数据
 * @returns {Promise}
 */
function loadDict(dict, dictMeta) {
  return dictMeta.request(dictMeta)
    .then(response => {
      const type = dictMeta.type
      let dicts = dictMeta.responseConverter(response, dictMeta)
      if (!(dicts instanceof Array)) {
        console.error('the return of responseConverter must be Array.<DictData>')
        dicts = []
      } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) {
        console.error('the type of elements in dicts must be DictData')
        dicts = []
      }
      dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts)
      dicts.forEach(d => {
        Vue.set(dict.label[type], d.value, d.label)
      })
      return dicts
    })
}

这样字典管理插件实现思路就基本清楚了,希望对你有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/408188.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

chrome插件开发时跨域问题解决方案

这是一个没有套路的前端博主&#xff0c;热衷各种前端向的骚操作&#xff0c;经常想到哪就写到哪&#xff0c;如果有感兴趣的技术和前端效果可以留言&#xff5e;博主看到后会去代替大家踩坑的&#xff5e;接下来的几篇都是uni-app的小实战&#xff0c;有助于我们更好的去学习u…

Vue在HTML中如何使用

&#x1f440;Vue是什么 一套用于构建用户界面的渐进式JavaScript框架。 构建用户界面&#xff1a;数据变成界面渐进式&#xff1a;Vue可以自底向上逐层的应用&#x1f440;Vue如何使用 一、引入vue.js <script src"./js/vue.js"></script> 二、准备一个…

HBuilderX uni-app简单实现静态登录页面(实例)

本章用到......uni-app页面跳转uni.navigateTo方法、uni.navigateBack方法。uni-app简单实现邮箱验证码发送点击后读秒样式。登录账号、密码正则表达式验证等 适合刚入门的小伙伴&#xff0c;大佬就没必要看了 静态页面&#xff01;静态页面&#xff01;没有绑定后端数据接口…

拿来即用的前端登录页面(简洁清爽版)

1、使用bootstrap实现 代码&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>登录</title><link rel"stylesheet" href"/bootstrap-3.3.7-dist/css/bootstrap.m…

vue项目根据不同环境动态配置接口请求ip及全局变量(vue环境变量配置)

在项目的开发过程中&#xff0c;我们常常会遇到根据不同的环境需要切换不同的ip的问题&#xff0c;例如在项目部署到测试服时需要将接口请求ip替换成测试服的ip,部署到正式服时又需要将接口请求ip替换成正式服对应的ip,有些公司还有预发环境等&#xff0c;这样在每次部署不同环…

Vue实战篇三十五:实现滑动拼图验证登录

系列文章目录 Vue基础篇一&#xff1a;编写第一个Vue程序 Vue基础篇二&#xff1a;Vue组件的核心概念 Vue基础篇三&#xff1a;Vue的计算属性与侦听器 Vue基础篇四&#xff1a;Vue的生命周期&#xff08;秒杀案例实战&#xff09; Vue基础篇五&#xff1a;Vue的指令 Vue基础篇…

Please set spring.main.web-application-type=reactive or remove spring-boot-starter-web dependency.

一、问题在启动springcloud的gateway模块的时候报错Please set spring.main.web-application-typereactive or remove spring-boot-starter-web dependency.二、问题产生的原因gateway组件中的 spring-boot-starter-webflux 和 springboot作为web项目启动必不可少的 spring-boo…

前端如何将静态页面部署到服务器,并可以通过公网ip访问。

问题描述 作为卑微的前端页面仔。在我们公司项目上线的时候&#xff0c;一般都是我们前端 npm run build&#xff0c;然后直接把打出来的dist包丢给后端&#xff0c;后端上传到服务器完成前端的部署。这个时候我就很好奇&#xff0c;页面是怎么上传到服务器的呢&#xff1f;上…

vue使用pinia (vue2/vue3)

pinia是什么&#xff1f;Pinia 是 Vue.js 的轻量级状态管理库 官方网站&#xff1a;Pinia 中文文档: 介绍 | Pinia 中文文档 pinia与vuex4 相同 是vue 官方 状态管理工具(作者是 Vue 核心团队成员&#xff09;是vue开发者工具支持pinia 不同 pinia相比vuex4&#xff0c…

云E办Springboot+vue——前端项目完整版(含源码)

一、项目简介 项目背景&#xff1a;受疫情的影响&#xff0c;许多企业由线上办公转为线下办公。随着线上办公的人数的增多&#xff0c;线上办公的优点逐步凸显&#xff1a;通过实现工作流程的自动化、节省企业办公费用、实现绿色办公&#xff0c;同时提升办公效率。 项目介绍…

uniapp在小程序中登录,获取用户信息,获取手机号逻辑记录

这里写目录标题概述uniapp小程序的授权描述授权的详细说明及使用1、微信小程序通过uni.login()方法可以获取到微信提供的code2、通过登录获取的code码可以以获取用户唯一标识openid以及会话密钥sessionkey用于解密获取手机的加密信息3、通过微信提供的获取微信手机号的方法getp…

ELK企业级日志分析平台(二)

文章目录一、kibana数据可视化1.部署2.定制数据可视化&#xff08;1&#xff09;网站访问量&#xff08;2&#xff09;访问量排行榜&#xff08;3&#xff09;创建dashboard&#xff0c;大屏展示二、ES集群监控1.启用xpack认证2.metricbeat监控3.filebeat日志采集一、kibana数据…

【玩转CSS】一文带你了解浮动

&#x1f525;一个人走得远了&#xff0c;就会忘记自己为了什么而出发&#xff0c;希望你可以不忘初心&#xff0c;不要随波逐流&#xff0c;一直走下去&#x1f3b6; &#x1f98b; 欢迎关注&#x1f5b1;点赞&#x1f44d;收藏&#x1f31f;留言&#x1f43e; &#x1f984; …

牛客前端刷题(六)—— JS基础

还在担心面试不通过吗?给大家推荐一个超级好用的刷面试题神器:牛客网,里面涵盖了各个领域的面试题库,还有大厂真题哦! 赶快悄悄的努力起来吧,不苒在这里衷心祝愿各位大佬都能顺利通过面试。 面试专栏分享,感觉有用的小伙伴可以点个订阅,不定时更新相关面试题:面试专栏…

前端面试中经常提到的LRU缓存策略详解

&#x1f431; 个人主页&#xff1a;不叫猫先生 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步&#xff0c;一起加油呀&#xff01; &am…

微信小程序解决view点击事件穿透地图map触发markertap

微信小程序中使用map组件&#xff0c;ios手机中点击地图上的view&#xff0c;会触发底下的markertap&#xff0c;只要底下如果有marker点的话。 这就造成了用户体验不是很好。 然后无意间我发现点击能滑动的scroll-view反而不会触发底下的markertap&#xff0c;就等于是一个不…

HTML介绍以及常用代码

HTML 网页基础 html(Hyper Text Markup Language)超文本标 记语言&#xff0c;发明者: Tim Berners-leehtml主要是定义网页内容和结构的。html是编 写网页的语言。html只能运行在浏览器上面网页的技术包含: html(编写网页结构&#xff0c;类似人 的骨架)&#xff0c;css(层叠…

前端启动项目npm run dev报错npm ERR! missing script: dev

今天遇到了这样一个nt问题 突然前端跑不起来后面发现是进行npm run dev 命令的时候少进入一层目录 进去之后就可以了对此遇到这个bug我还查了很多blog 发现还有以下两种原因1.打开的是当前文件夹&#xff0c;但是文件夹package.js里的scripts确实没有dev,输入vue init webpack …

【node进阶】深入浅出前后端身份验证(下)---JWT

✅ 作者简介&#xff1a;一名普通本科大三的学生&#xff0c;致力于提高前端开发能力 ✨ 个人主页&#xff1a;前端小白在前进的主页 &#x1f525; 系列专栏 &#xff1a; node.js学习专栏 ⭐️ 个人社区 : 个人交流社区 &#x1f340; 学习格言: ☀️ 打不倒你的会使你更强&a…

【JavaScript】JS实用案例分享:选择器组件 | 简易计算器

&#x1f5a5;️ NodeJS专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ 博主的前端之路&#xff08;源创征文一等奖作品&#xff09;&#xff1a;前端之行&#xff0c;任重道远&#xff08;来自大三学长的万字自述&#xff09; &#x1f5a5;️ TypeScript知识总结&…