refactor(tree): 菜单与部门树构建抽为通用 toTree 工具

- 菜单页、权限分配页与部门页的递归转换收敛为节点映射回调
- 节点字段与叶子 children 形态由调用方决定,输出与原实现一致
This commit is contained in:
Ray.Hao committed 2026-10-16 17:56:20 +08:00
1 parent 5cb4b31173
commit ac58ec49cb
4 files changed
+62 -50

No files matched your search

+13 -13
View File
@@ -102,7 +102,7 @@
<script lang="ts" setup>
import { onLoad } from "@dcloudio/uni-app";
import { toFormSchema } from "@/utils/form-schema";
import { findOptionChain } from "@/utils/tree";
import { findOptionChain, toTree } from "@/utils/tree";
import { useToast } from "@wot-ui/ui";
import type { CascaderOption } from "@wot-ui/ui/components/wd-cascader/types";
import ActionSheet, { type ActionSheetCommand } from "@/subPages/work/components/ActionSheet.vue";
@@ -148,20 +148,20 @@ interface DeptTreeOption extends TreeOption {
// 转换为树组件数据格式
const treeData = computed<DeptTreeOption[]>(() =>
deptList.value.map((dept) => transformDeptToTree(dept))
toTree(
deptList.value,
(dept) => dept.children,
(dept, children) => ({
value: String(dept.id),
label: dept.name ?? "",
id: dept.id,
name: dept.name,
status: dept.status,
children,
})
)
);
function transformDeptToTree(dept: DeptItem): DeptTreeOption {
return {
value: String(dept.id),
label: dept.name ?? "",
id: dept.id,
name: dept.name,
status: dept.status,
children: dept.children?.map((child) => transformDeptToTree(child)) || [],
};
}
// 上级部门选择器
const showParentPicker = ref(false);
const parentSelected = ref<string | number>("");
+16 -17
View File
@@ -157,7 +157,7 @@
<script lang="ts" setup>
import { onLoad } from "@dcloudio/uni-app";
import { toFormSchema } from "@/utils/form-schema";
import { findOptionChain } from "@/utils/tree";
import { findOptionChain, toTree } from "@/utils/tree";
import { useToast } from "@wot-ui/ui";
import ActionSheet, { type ActionSheetCommand } from "@/subPages/work/components/ActionSheet.vue";
import { useConfirmAction } from "@/composables/useConfirmAction";
@@ -230,24 +230,23 @@ const MENU_TYPE_TEXT: Record<MenuType, string> = {
// 菜单列表转树节点:value/label 供树组件使用,业务字段随节点透传给操作菜单
const treeData = computed<MenuTreeOption[]>(() =>
menuList.value.map((menu: MenuItem) => transformMenuToTree(menu))
toTree(
menuList.value,
(menu) => menu.children,
(menu, children) => ({
value: menu.id ?? "",
label: menu.name ?? "",
id: menu.id,
name: menu.name,
type: menu.type,
icon: menu.icon,
visible: menu.visible,
perm: menu.perm,
children,
})
)
);
// 递归转换:子节点沿用同一套字段,空 id 用空串占位以满足树组件契约
function transformMenuToTree(menu: MenuItem): MenuTreeOption {
return {
value: menu.id ?? "",
label: menu.name ?? "",
id: menu.id,
name: menu.name,
type: menu.type,
icon: menu.icon,
visible: menu.visible,
perm: menu.perm,
children: menu.children?.map((child: MenuItem) => transformMenuToTree(child)) || [],
};
}
// 菜单类型对应的标签颜色,类型缺失时按主色展示
function getMenuTypeTag(type?: MenuType) {
return (type && MENU_TYPE_TAG[type]) || "primary";
+15 -20
View File
@@ -78,6 +78,7 @@
<script lang="ts" setup>
import { onLoad } from "@dcloudio/uni-app";
import MenuAPI, { type MenuItem, type MenuType } from "@/api/menu";
import { toTree } from "@/utils/tree";
import RoleAPI from "@/api/role";
import { getErrorMessage } from "@/utils/error";
import TreeView from "@/subPages/work/components/TreeView.vue";
@@ -104,27 +105,21 @@ interface PermissionTreeOption extends TreeOption {
children?: PermissionTreeOption[];
}
// 转换菜单为树结构
const menuTree = computed<PermissionTreeOption[]>(() => {
if (!menuList.value.length) return [];
return transformMenuToTree(menuList.value);
});
// 转换菜单为树组件格式
function transformMenuToTree(menus: MenuItem[]): PermissionTreeOption[] {
return menus.map((menu) => {
const id = String(menu.id);
const name = menu.name ?? "";
return {
value: id,
label: name,
id,
name,
// 转换菜单为树组件格式,叶子节点不携带 children 字段
const menuTree = computed<PermissionTreeOption[]>(() =>
toTree(
menuList.value,
(menu) => menu.children,
(menu, children) => ({
value: String(menu.id),
label: menu.name ?? "",
id: String(menu.id),
name: menu.name ?? "",
type: menu.type,
children: menu.children ? transformMenuToTree(menu.children) : undefined,
};
});
}
children: menu.children ? children : undefined,
})
)
);
// 处理选中变化
function handleCheck(keys: string[]) {
+18
View File
@@ -24,3 +24,21 @@ export function findOptionChain(
}
return null;
}
/**
* 递归把带 children 的列表转为树节点
*
* 源列表的层级由 children 字段表达,节点字段与叶子形态由调用方决定
*
* @param items 源列表
* @param getChildren 取节点的子列表,无子级时返回 undefined
* @param node 单节点映射,children 为已构建的子节点数组,叶子节点为空数组
* @returns 树节点列表
*/
export function toTree<TSource, TNode>(
items: TSource[],
getChildren: (item: TSource) => TSource[] | undefined,
node: (item: TSource, children: TNode[]) => TNode
): TNode[] {
return items.map((item) => node(item, toTree(getChildren(item) ?? [], getChildren, node)));
}