mirror of
https://gitee.com/dapppp/ruoyi-plus-vben5.git
synced 2026-03-19 06:28:50 +08:00
merge
This commit is contained in:
@@ -94,4 +94,32 @@ function mapTree<T, V extends Record<string, any>>(
|
||||
});
|
||||
}
|
||||
|
||||
export { filterTree, mapTree, traverseTreeValues };
|
||||
/**
|
||||
* 对树形结构数据进行递归排序
|
||||
* @param treeData - 树形数据数组
|
||||
* @param sortFunction - 排序函数,用于定义排序规则
|
||||
* @param options - 配置选项,包括子节点属性名
|
||||
* @returns 排序后的树形数据
|
||||
*/
|
||||
function sortTree<T extends Record<string, any>>(
|
||||
treeData: T[],
|
||||
sortFunction: (a: T, b: T) => number,
|
||||
options?: TreeConfigOptions,
|
||||
): T[] {
|
||||
const { childProps } = options || {
|
||||
childProps: 'children',
|
||||
};
|
||||
|
||||
return treeData.toSorted(sortFunction).map((item) => {
|
||||
const children = item[childProps];
|
||||
if (children && Array.isArray(children) && children.length > 0) {
|
||||
return {
|
||||
...item,
|
||||
[childProps]: sortTree(children, sortFunction, options),
|
||||
};
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
|
||||
export { filterTree, mapTree, sortTree, traverseTreeValues };
|
||||
|
||||
@@ -73,6 +73,7 @@ function handleClick(menu: IContextMenuItem) {
|
||||
>
|
||||
<template v-for="menu in menusView" :key="menu.key">
|
||||
<ContextMenuItem
|
||||
v-if="!menu.hidden"
|
||||
:class="itemClass"
|
||||
:disabled="menu.disabled"
|
||||
:inset="menu.inset || !menu.icon"
|
||||
|
||||
@@ -10,6 +10,10 @@ interface IContextMenuItem {
|
||||
* @param data
|
||||
*/
|
||||
handler?: (data: any) => void;
|
||||
/**
|
||||
* @zh_CN 是否隐藏
|
||||
*/
|
||||
hidden?: boolean;
|
||||
/**
|
||||
* @zh_CN 图标
|
||||
*/
|
||||
|
||||
@@ -27,7 +27,7 @@ function handleItemClick(value: string) {
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="start">
|
||||
<DropdownMenuGroup>
|
||||
<template v-for="menu in menus" :key="menu.key">
|
||||
<template v-for="menu in menus" :key="menu.value">
|
||||
<DropdownMenuItem
|
||||
:class="
|
||||
menu.value === modelValue
|
||||
|
||||
@@ -36,6 +36,8 @@ interface Props {
|
||||
childrenField?: string;
|
||||
/** value字段名 */
|
||||
valueField?: string;
|
||||
/** disabled字段名 */
|
||||
disabledField?: string;
|
||||
/** 组件接收options数据的属性名 */
|
||||
optionsPropName?: string;
|
||||
/** 是否立即调用api */
|
||||
@@ -75,6 +77,7 @@ defineOptions({ name: 'ApiComponent', inheritAttrs: false });
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
labelField: 'label',
|
||||
valueField: 'value',
|
||||
disabledField: 'disabled',
|
||||
childrenField: '',
|
||||
optionsPropName: 'options',
|
||||
resultField: '',
|
||||
@@ -108,17 +111,25 @@ const isFirstLoaded = ref(false);
|
||||
const hasPendingRequest = ref(false);
|
||||
|
||||
const getOptions = computed(() => {
|
||||
const { labelField, valueField, childrenField, numberToString } = props;
|
||||
const {
|
||||
labelField,
|
||||
valueField,
|
||||
disabledField,
|
||||
childrenField,
|
||||
numberToString,
|
||||
} = props;
|
||||
|
||||
const refOptionsData = unref(refOptions);
|
||||
|
||||
function transformData(data: OptionsItem[]): OptionsItem[] {
|
||||
return data.map((item) => {
|
||||
const value = get(item, valueField);
|
||||
const disabled = get(item, disabledField);
|
||||
return {
|
||||
...objectOmit(item, [labelField, valueField, childrenField]),
|
||||
...objectOmit(item, [labelField, valueField, disabled, childrenField]),
|
||||
label: get(item, labelField),
|
||||
value: numberToString ? `${value}` : value,
|
||||
disabled: get(item, disabledField),
|
||||
...(childrenField && item[childrenField]
|
||||
? { children: transformData(item[childrenField]) }
|
||||
: {}),
|
||||
|
||||
@@ -23,6 +23,7 @@ export {
|
||||
VbenButtonGroup,
|
||||
VbenCheckbox,
|
||||
VbenCheckButtonGroup,
|
||||
VbenContextMenu,
|
||||
VbenCountToAnimator,
|
||||
VbenFullScreen,
|
||||
VbenInputPassword,
|
||||
|
||||
@@ -488,6 +488,6 @@ async function handleReset() {
|
||||
:deep(.sticky-tabs-header [role='tablist']) {
|
||||
position: sticky;
|
||||
top: -12px;
|
||||
z-index: 10;
|
||||
z-index: 9999;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -13,10 +13,28 @@ function parseSvg(svgData: string): IconifyIconStructure {
|
||||
const xmlDoc = parser.parseFromString(svgData, 'image/svg+xml');
|
||||
const svgElement = xmlDoc.documentElement;
|
||||
|
||||
// 提取 SVG 根元素的关键样式属性
|
||||
const getAttrs = (el: Element, attrs: string[]) =>
|
||||
attrs
|
||||
.map((attr) =>
|
||||
el.hasAttribute(attr) ? `${attr}="${el.getAttribute(attr)}"` : '',
|
||||
)
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
|
||||
const rootAttrs = getAttrs(svgElement, [
|
||||
'fill',
|
||||
'stroke',
|
||||
'fill-rule',
|
||||
'stroke-width',
|
||||
]);
|
||||
|
||||
const svgContent = [...svgElement.childNodes]
|
||||
.filter((node) => node.nodeType === Node.ELEMENT_NODE)
|
||||
.map((node) => new XMLSerializer().serializeToString(node))
|
||||
.join('');
|
||||
// 若根有属性,用一个 g 标签包裹内容并继承属性
|
||||
const body = rootAttrs ? `<g ${rootAttrs}>${svgContent}</g>` : svgContent;
|
||||
|
||||
const viewBoxValue = svgElement.getAttribute('viewBox') || '';
|
||||
const [left, top, width, height] = viewBoxValue.split(' ').map((val) => {
|
||||
@@ -25,7 +43,7 @@ function parseSvg(svgData: string): IconifyIconStructure {
|
||||
});
|
||||
|
||||
return {
|
||||
body: svgContent,
|
||||
body,
|
||||
height,
|
||||
left,
|
||||
top,
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
"length": "{0} must be {1} characters long",
|
||||
"alreadyExists": "{0} `{1}` already exists",
|
||||
"startWith": "{0} must start with `{1}`",
|
||||
"invalidURL": "Please input a valid URL"
|
||||
"invalidURL": "Please input a valid URL",
|
||||
"sizeLimit": "The file size cannot exceed {0}MB",
|
||||
"previewWarning": "Unable to open the file, there is no available URL or preview address"
|
||||
},
|
||||
"actionTitle": {
|
||||
"edit": "Modify {0}",
|
||||
@@ -24,7 +26,8 @@
|
||||
},
|
||||
"placeholder": {
|
||||
"input": "Please enter",
|
||||
"select": "Please select"
|
||||
"select": "Please select",
|
||||
"upload": "Click to upload"
|
||||
},
|
||||
"captcha": {
|
||||
"title": "Please complete the security verification",
|
||||
|
||||
@@ -7,7 +7,9 @@
|
||||
"length": "{0}长度必须为{1}个字符",
|
||||
"alreadyExists": "{0} `{1}` 已存在",
|
||||
"startWith": "{0}必须以 {1} 开头",
|
||||
"invalidURL": "请输入有效的链接"
|
||||
"invalidURL": "请输入有效的链接",
|
||||
"sizeLimit": "文件大小不能超过 {0}MB",
|
||||
"previewWarning": "无法打开文件,没有可用的URL或预览地址"
|
||||
},
|
||||
"actionTitle": {
|
||||
"edit": "修改{0}",
|
||||
@@ -24,7 +26,8 @@
|
||||
},
|
||||
"placeholder": {
|
||||
"input": "请输入",
|
||||
"select": "请选择"
|
||||
"select": "请选择",
|
||||
"upload": "点击上传"
|
||||
},
|
||||
"captcha": {
|
||||
"title": "请完成安全验证",
|
||||
|
||||
@@ -6,7 +6,7 @@ import type {
|
||||
RouteMeta,
|
||||
} from '@vben-core/typings';
|
||||
|
||||
import { filterTree, mapTree } from '@vben-core/shared/utils';
|
||||
import { filterTree, mapTree, sortTree } from '@vben-core/shared/utils';
|
||||
|
||||
/**
|
||||
* 根据 routes 生成菜单列表
|
||||
@@ -81,7 +81,7 @@ function generateMenus(
|
||||
});
|
||||
|
||||
// 对菜单进行排序,避免order=0时被替换成999的问题
|
||||
menus = menus.toSorted((a, b) => (a?.order ?? 999) - (b?.order ?? 999));
|
||||
menus = sortTree(menus, (a, b) => (a?.order ?? 999) - (b?.order ?? 999));
|
||||
|
||||
// 过滤掉隐藏的菜单项
|
||||
return filterTree(menus, (menu) => !!menu.show);
|
||||
|
||||
Reference in New Issue
Block a user