当前位置: 首页 > news >正文

vben ruoyi 数据字典解决方案

vben ruoyi 数据字典解决方案

原作者声明 玲娜贝er的gitee

使用示例和效果

示例:

operlog.data.tsx 你也可以在data.ts中使用 作者这个文件包含JSX语法,所以是tsx文件

import { BasicColumn } from '@/components/Table';
import { useRender } from '@/hooks/component/useRender';
import { FormSchema } from '@/components/Form';
import { getDictOptions } from '@/utils/dict';
import { DictEnum } from '@/enums/dictEnum';
import { DescItem } from '@/components/Description';const { renderJsonPreview, renderHttpMethodTag, renderBoldText, renderDict, renderTag } =useRender();export const columns: BasicColumn[] = [{title: '系统模块',dataIndex: 'title',},{title: '操作类型',// 需要从字典获取dataIndex: 'businessType',customRender({ value }) {return renderDict(value, DictEnum.SYS_OPER_TYPE);},},{title: '操作人员',dataIndex: 'operName',},{title: 'IP地址',dataIndex: 'operIp',},{title: 'IP信息',dataIndex: 'operLocation',},{title: '操作状态',dataIndex: 'status',customRender({ value }) {return renderDict(value, DictEnum.COMMON_STATUS);},},{title: '操作日期',dataIndex: 'operTime',sorter: true,},{title: '操作耗时',dataIndex: 'costTime',sorter: true,customRender({ text }) {return `${text} ms`;},},
];export const formSchemas: FormSchema[] = [{field: 'title',label: '系统模块',component: 'Input',},{field: 'operName',label: '操作人员',component: 'Input',},{field: 'businessType',label: '操作类型',component: 'Select',componentProps: {options: getDictOptions(DictEnum.SYS_OPER_TYPE),},},{field: 'operIp',label: '操作IP',component: 'Input',},{field: 'status',label: '状态',component: 'Select',componentProps: {options: getDictOptions(DictEnum.COMMON_STATUS),},},{field: 'createTime',label: '操作时间',component: 'RangePicker',},
];export const descSchema: DescItem[] = [{field: 'operId',label: '日志编号',},{field: 'status',label: '操作结果',render(value) {return renderDict(value, DictEnum.COMMON_STATUS);},},{field: 'title',label: '操作模块',labelMinWidth: 80,render(value, { businessType }) {const operType = renderDict(businessType, DictEnum.SYS_OPER_TYPE);const moduleName = renderTag(value);// 改为tsx书写return (<span>{moduleName}{operType}</span>);},},{field: 'operIp',label: '操作信息',render(_, data) {return `账号: ${data.operName} / ${data.deptName} / ${data.operIp} / ${data.operLocation}`;},},{field: 'operUrl',label: '请求信息',render(_, data) {const { operUrl, requestMethod } = data;const methodTag = renderHttpMethodTag(requestMethod);return (<span>{methodTag} {operUrl}</span>);},},{field: 'errorMsg',label: '异常信息',show: (data) => {return data && data.errorMsg;},render(value) {return renderBoldText(value, 'text-red-600');},},{field: 'method',label: '方法',},{field: 'operParam',label: '请求参数',render(value) {return renderJsonPreview(value);},},{field: 'jsonResult',label: '响应参数',show(data) {return data && data.jsonResult;},render(value) {return renderJsonPreview(value);},},{field: 'costTime',label: '耗时',render(value) {return `${value} ms`;},},{field: 'operTime',label: '操作时间',},
];

效果图

在这里插入图片描述

核心目录文件

api/system/dict/dictData.model.ts
utils/dict.ts
store/modules/dict.ts
hooks/component/useRender.tsx
components/Dict/index.ts
components/Dict/src/index.vue
components/Dict/src/data.ts
enums/dictEnum.ts

api/system/dict/dictData.model.ts 数据字典数据 interface

根据实际自己的来修改

export interface DictData {createBy: string;createTime: string;updateBy?: any;updateTime?: any;remark: string;dictCode: number;dictSort: number;dictLabel: string;dictValue: string;dictType: string;cssClass: string;listClass: string;isDefault: string;status: string;default: boolean;code: number | string | boolean;name: string;
}

utils/dict.ts 数据字典工具类

import { useDictStoreWithOut, Option, dictToOptions } from '@/store/modules/dict';
import { dictDataInfo } from '@/api/system/dict/dictData'; // 自己的api
import { DictData } from '@/api/system/dict/dictData.model';// ts类型/*** 添加一个字典请求状态的缓存** 主要解决多次请求重复api的问题(不能用abortController 会导致除了第一个其他的获取的全为空)* 比如在一个页面 index表单 modal drawer总共会请求三次 但是获取的都是一样的数据*/
const dictRequestCache = new Map<string, Promise<void | DictData[]>>();export function getDict(dictName: string): DictData[] {console.log(dictName, 'dictName');const { getDict, setDictInfo } = useDictStoreWithOut();// 这里拿到const dictList: DictData[] = getDict(dictName);if (dictList.length === 0) {// 检查请求状态缓存if (!dictRequestCache[dictName]) {dictRequestCache[dictName] = dictDataInfo(dictName).then((resp) => {// 缓存到store 这样就不用重复获取了setDictInfo(dictName, resp);// 这里不可用dictList = respdictList.push(...resp);// 移除请求状态缓存dictRequestCache.delete(dictName);});}}return dictList;
}export function getDictOptions(dictName: string): Option[] {const { getDictOptions, setDictInfo } = useDictStoreWithOut();const dictOptionList = getDictOptions(dictName);if (dictOptionList.length === 0) {// 检查请求状态缓存if (!dictRequestCache[dictName]) {dictRequestCache[dictName] = dictDataInfo(dictName).then((resp) => {// 缓存到store 这样就不用重复获取了setDictInfo(dictName, resp);// 转为optionsconst option = dictToOptions(resp);// 这里不可用dictOptionList = optiondictOptionList.push(...option);// 移除请求状态缓存dictRequestCache.delete(dictName);});}}return dictOptionList;
}

store/modules/dict.ts 数据字典store

import { defineStore } from 'pinia';
import { DictData } from '@/api/system/dict/dictData.model';
import { store } from '@/store';
import { isEmpty } from '@/utils/is'; export type Option = { label: string; value: string; disabled?: boolean };export function dictToOptions(data: DictData[]): Option[] {if (!isEmpty(data)) {return [];}return data.map((item) => ({label: item.dictLabel,value: item.dictValue,}));
}interface DictState {// 一般是dictTag使用dictMap: Map<string, DictData[]>;// select radio radioButton使用 只能为固定格式(Option)dictOptionsMap: Map<string, Option[]>;
}export const useDictStore = defineStore('dict', {state: (): DictState => ({dictMap: new Map(),dictOptionsMap: new Map(),}),getters: {getDictMap(state) {return state.dictMap;},getDictOptionsMap(state) {return state.dictOptionsMap;},},actions: {setDictInfo(dictName: string, dictValue: DictData[]): void {this.dictMap.set(dictName, dictValue);this.dictOptionsMap.set(dictName, dictToOptions(dictValue));},getDict(dictName: string): DictData[] {if (!dictName) return [];// 没有key 添加一个空数组if (!this.dictMap.has(dictName)) {this.dictMap.set(dictName, []);}// 这里拿到的就不可能为空了return this.dictMap.get(dictName)!;},getDictOptions(dictName: string): Option[] {if (!dictName) return [];// 没有key 添加一个空数组if (!this.dictOptionsMap.has(dictName)) {this.dictOptionsMap.set(dictName, []);}// 这里拿到的就不可能为空了return this.dictOptionsMap.get(dictName)!;},resetCache() {this.dictMap.clear();this.dictOptionsMap.clear();},},
});export function useDictStoreWithOut() {return useDictStore(store);
}

hooks/component/useRender.tsx 组件渲染hooks

我只使用了文件中的 renderTag,renderTags, renderDictTags,renderDict方法,注意自己食用

import { Tag, Button, Tooltip, Switch } from 'ant-design-vue';
import { JsonPreview } from '@/components/CodeEditor';
import Icon from '@/components/Icon/Icon.vue';
import { DictTag } from '@/components/Dict';
import { DictData } from '@/api/system/dict/dictData.model';
import { formatToDate, formatToDateTime } from '@/utils/dateUtil';
import { useGo } from '@/hooks/web/usePage';
import { TooltipPlacement } from 'ant-design-vue/lib/tooltip';
import { getDict } from '@/utils/dict';
import { useMessage } from '@/hooks/web/useMessage';function renderTag(text: string, color?: string) {return <Tag color={color}>{text}</Tag>;
}/**** @param tags 标签list* @param wrap 是否换行显示* @param [gap=1] 间隔* @returns*/
function renderTags(tags: string[], wrap = false, gap = 1) {return (<div class={['flex', `gap-${gap}`, wrap ? 'flex-col' : 'flex-row']}>{tags.map((tag, index) => {return (<div class={[`mb-1`, `mr-1`]} key={index}>{renderTag(tag)}</div>);})}</div>);
}/**** @param json json对象 接受object/string类型* @returns json预览*/
function renderJsonPreview(json: any) {if (typeof json !== 'object' && typeof json !== 'string') {return <span>{json}</span>;}if (typeof json === 'object') {return <JsonPreview data={json} />;}try {const obj = JSON.parse(json);// 基本数据类型可以被转为jsonif (typeof obj !== 'object') {return <span>{obj}</span>;}return <JsonPreview data={obj} />;} catch (e) {return <span>{json}</span>;}
}// 图标
function renderIcon(icon: string) {return <Icon icon={icon}></Icon>;
}// httpMethod
function renderHttpMethodTag(type: string) {const method = type.toUpperCase();let color = 'default';const title = method + '请求';switch (method) {case 'GET':color = 'green';break;case 'POST':color = 'blue';break;case 'PUT':color = 'orange';break;case 'DELETE':color = 'red';break;}return <Tag color={color}>{title}</Tag>;
}function renderDictTag(value: string, dicts: DictData[]) {return <DictTag dicts={dicts} value={value}></DictTag>;
}/*** render多个dictTag* @param value key数组 string[]类型* @param dicts 字典数组* @param wrap 是否需要换行显示* @param [gap=1] 间隔* @returns render*/
function renderDictTags(value: string[], dicts: DictData[], wrap = true, gap = 1) {if (!Array.isArray(value)) {return <div>{value}</div>;}return (<div class={['flex', `gap-${gap}`, wrap ? 'flex-col' : 'flex-row']}>{value.map((item, index) => {return <div key={index}>{renderDictTag(item, dicts)}</div>;})}</div>);
}/*** @param value 值* @param dictName dictName* @returns tag*/
function renderDict(value: string, dictName: string) {const dictInfo = getDict(dictName);return renderDictTag(value, dictInfo);
}/*** 加粗文字* @param value 文字* @param colorCss 颜色 使用windicss类名 如text-red-500* @returns*/
function renderBoldText(value: string, colorCss?: string) {return <span class={['font-bold', colorCss]}>{value}</span>;
}function renderIconSpan(icon: string, value: string, center = false, marginLeft = '2px') {const justifyCenter = center ? 'justify-center' : '';return (<span class={['flex', 'items-center', justifyCenter]}>{renderIcon(icon)}<span style={{ marginLeft }}>{value}</span></span>);
}function renderDate(date: string) {return formatToDate(date);
}function renderDateTime(date: string) {return formatToDateTime(date);
}function renderHref(value: string, path: string) {const go = useGo();return (<Button type="link" onClick={() => go(path)}>{value}</Button>);
}function renderTooltip(value: string, placement: TooltipPlacement = 'top') {return (<Tooltip placement={placement} title={value}>{value}</Tooltip>);// return h(Tooltip, { placement, title: value }, () => value);
}const { createConfirm } = useMessage();
/*** feat 目前无法处理reload* 封装switch 用于表格内状态的切换* @param record record(reactive)* @param api 切换状态的api  默认传参是record* @param customContent 自定义内容 回调参数为新状态的str* @returns*/
function renderSwitch(record: Recordable,api: (...data: any) => Promise<any>,customContent?: (newStatusStr: string) => string,reload?: () => Promise<any>,
) {return (<Switchchecked={record.status}checked-children="启用"un-checked-children="禁用"checkedValue="0"unCheckedValue="1"onChange={(newStatus) => {const lastStatus = record.status;const newStatusStr = newStatus === '0' ? '启用' : '禁用';// 切换状态record.status = newStatus;// 自定义内容const defaultContent = `确认${newStatusStr}吗?`;const content = customContent ? customContent(newStatusStr) : defaultContent;createConfirm({title: '提示',iconType: 'warning',content,async onOk() {try {await api(record);await reload?.();} catch (e) {record.status = lastStatus;}},onCancel() {record.status = lastStatus;},});}}></Switch>);
}const osOptions = [{ icon: 'devicon:windows8', value: 'windows' },{ icon: 'devicon:linux', value: 'linux' },{ icon: 'wpf:macos', value: 'osx' },{ icon: 'flat-color-icons:android-os', value: 'android' },{ icon: 'majesticons:iphone-x-apps-line', value: 'iphone' },
];/*** 浏览器图标* cn.hutool.http.useragent -> browers*/
const browserOptions = [{ icon: 'logos:chrome', value: 'chrome' },{ icon: 'logos:microsoft-edge', value: 'edge' },{ icon: 'logos:firefox', value: 'firefox' },{ icon: 'logos:opera', value: 'opera' },{ icon: 'logos:safari', value: 'safari' },{ icon: 'mdi:wechat', value: 'micromessenger' },{ icon: 'logos:quarkus-icon', value: 'quark' },{ icon: 'mdi:wechat', value: 'wxwork' },{ icon: 'simple-icons:tencentqq', value: 'qq' },{ icon: 'ri:dingding-line', value: 'dingtalk' },{ icon: 'arcticons:uc-browser', value: 'uc' },{ icon: 'ri:baidu-fill', value: 'baidu' },
];function renderOsIcon(os: string, center = false) {if (!os) {return;}let current = osOptions.find((item) => os.toLocaleLowerCase().includes(item.value));// windows要特殊处理if (os.toLocaleLowerCase().includes('windows')) {current = osOptions[0];}if (current) {return renderIconSpan(current.icon, os, center, '5px');}// 返回默认const defaultIcon = 'ic:outline-computer';return renderIconSpan(defaultIcon, os, center, '5px');
}function renderBrowserIcon(browser: string, center = false) {if (!browser) {return;}const current = browserOptions.find((item) => browser.toLocaleLowerCase().includes(item.value));if (current) {return renderIconSpan(current.icon, browser, center, '5px');}// 返回默认const defaultIcon = 'ph:browser-duotone';return renderIconSpan(defaultIcon, browser, center, '5px');
}export function useRender() {return {renderTag,renderTags,renderJsonPreview,renderIcon,renderHttpMethodTag,renderDictTag,renderDictTags,renderDict,renderBoldText,renderIconSpan,renderDate,renderDateTime,renderHref,renderTooltip,renderSwitch,renderOsIcon,renderBrowserIcon,};
}

components/Dict 数据字典组件

注意:上面的tsx hook函数使用了该组件

1. components/Dict/index.ts 全局注册组件
import { withInstall } from '@/utils';
import dictTag from './src/index.vue';export const DictTag = withInstall(dictTag);
2. components/Dict/src/index.vue 数据字典组件vue文件
<template><Tag v-if="color" :color="color" :class="cssClass">{{ label }}</Tag><div v-if="!color" :class="cssClass">{{ label }}</div>
</template><script setup lang="ts">import { Tag } from 'ant-design-vue';import { computed } from 'vue';import { DictData } from '@/api/system/dict/dictData.model';import { tagTypes } from './data';defineOptions({ name: 'DictTag' });type DictTagProps = {dicts: DictData[]; // dict数组value: string | number; // value};const props = withDefaults(defineProps<DictTagProps>(), {dicts: undefined,});// 创建一个映射后的 dicts 数据const mappedDicts = computed(() => {return props.dicts.map((item) => ({...item,dictValue: item.code,dictLabel: item.name,}));});const color = computed<string>(() => {const current = mappedDicts.value.filter((item) => item.dictValue == props.value)[0];const listClass = current?.listClass;// 是否为默认的颜色const isDefault = Reflect.has(tagTypes, listClass);// 判断是默认还是自定义颜色if (isDefault) {// 这里做了antd - element-plus的兼容return tagTypes[listClass].color;}return listClass;});const cssClass = computed<string>(() => {const current = mappedDicts.value.filter((item) => item.dictValue == props.value)[0];return current?.cssClass ?? '';});const label = computed<string | number>(() => {const current = mappedDicts.value.filter((item) => item.dictValue == props.value)[0];return current?.dictLabel ?? 'unknown';});
</script><style scoped></style>
3. components/Dict/src/data.ts 数据文件
import { Tag } from 'ant-design-vue';
import { VNode, h } from 'vue';interface TagType {[key: string]: { label: string; color: string };
}export const tagTypes: TagType = {/** 由于和elementUI不同 用于替换颜色 */default: { label: '默认(default)', color: 'default' },primary: { label: '主要(primary)', color: 'processing' },success: { label: '成功(success)', color: 'success' },info: { label: '信息(info)', color: 'default' },warning: { label: '警告(warning)', color: 'warning' },danger: { label: '危险(danger)', color: 'error' },/** 自定义预设 color可以为16进制颜色 */pink: { label: 'pink', color: 'pink' },red: { label: 'red', color: 'red' },orange: { label: 'orange', color: 'orange' },green: { label: 'green', color: 'green' },cyan: { label: 'cyan', color: 'cyan' },purple: { label: 'purple', color: 'purple' },
};// 字典选择使用 { label: string; value: string }[]
interface Options {label: string | VNode;value: string;
}export const tagSelectOptions = () => {const selectArray: Options[] = [];Object.keys(tagTypes).forEach((key) => {const label = tagTypes[key].label;const color = tagTypes[key].color;selectArray.push({label: h(Tag, { color }, () => label),value: key,});});return selectArray;
};

enums/dictEnum.ts 数据字典枚举

export enum DictEnum {NORMAL_DISABLE = 'sys_normal_disable',COMMON_STATUS = 'sys_common_status',JOB_GROUP = 'sys_job_group', // 定时任务分组JOB_STATUS = 'sys_job_status', // 任务状态YES_NO = 'sys_yes_no', // 是否SYS_USER_SEX = 'sys_user_sex', // 性别SHOW_HIDE = 'sys_show_hide', // 显示状态NOTICE_TYPE = 'sys_notice_type', // 通知类型NOTICE_STATUS = 'sys_notice_status', // 通知状态SYS_OPER_TYPE = 'sys_oper_type', // 操作类型
}// 提供给下拉框组件使用
export const fieldNames = { label: 'dictLabel', value: 'dictValue' };

我这边使用的请求接口数据接口与作者不一样,自己调试的时候注意字段属性的修改。

http://www.dtcms.com/a/293270.html

相关文章:

  • 16.多生成树MSTP
  • Linux文件系统理解1
  • Selenium+Java 自动化测试入门到实践:从环境搭建到元素操作
  • ubuntu22.04 录视屏软件推荐
  • Three.js 实现梦幻星河流光粒子特效原理与实践
  • Redis 5.0中的 Stream是什么?
  • C语言(20250722)
  • 21. `taskSlotTable`和`jobLeaderService`启动
  • 使用空间数据训练机器学习模型的实用工作流程
  • An error occurred at line: 1 in the generated java file问题处理及tomcat指定对应的jdk运行
  • Dify工作流:爬虫文章到AI知识库
  • 【OD机试】数组和最大
  • Java基础环境配置
  • 从零开始学习大模型之文本数据处理
  • BEV-LaneDet
  • 网络编程---网络基础知识
  • 【文本分析】使用LDA模型进行主题建模——李牧南等(2024)《科研管理》、马鸿佳等(2025)《南开管理评论》的复现
  • 24. 两两交换链表中的节点
  • 线程池excutor 和 submit区别 关于异常处理,请详细说明,会吞掉异常吗,需要捕获吗
  • vue3:十八、内容管理-实现行内图片的预览、审核功能
  • Python--numpy基础知识
  • 海洋大地测量基准与水下导航系列之九我国海洋PNT最新技术进展(中)
  • Qt开发环境搭建全攻略(Windows+Linux+macOS)
  • 14.8 LLaMA2-7B×Dolly-15K实战:从准确率63%到89%,如何用优质数据让大模型性能飙升42%?
  • 17-VRRP
  • 汉诺塔问题
  • 阿里Seata事务模式场景化选型指南
  • Java学习-------事务失效
  • 第二章 JS进阶 【5. Date(日期对象)】
  • 坑机介绍学习研究