fix: correct menu activation status display
This commit is contained in:
1 parent
1efd0d1857
commit
041d65566e
3 files changed
+41
-5
No files matched your search
@@ -1,5 +1,6 @@
|
|||||||
import { describe, expect, it } from "vitest";
|
import { describe, expect, it } from "vitest";
|
||||||
import { buildSystemTree, dataScopeLabel, filterSystemTree } from "@/lib/system-resource";
|
import { buildSystemTree, dataScopeLabel, filterSystemTree, hydrateMenuTree } from "@/lib/system-resource";
|
||||||
|
import type { SystemMenuItem, SystemMenuTreeItem } from "@/types";
|
||||||
|
|
||||||
describe("system resource tree helpers", () => {
|
describe("system resource tree helpers", () => {
|
||||||
const items = [
|
const items = [
|
||||||
@@ -21,6 +22,28 @@ describe("system resource tree helpers", () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("hydrates menu tree nodes with status fields from the flat response", () => {
|
||||||
|
const flatItems = [{
|
||||||
|
id: "dashboard",
|
||||||
|
name: "仪表盘",
|
||||||
|
type: "page",
|
||||||
|
parentId: null,
|
||||||
|
sortOrder: 10,
|
||||||
|
isVisible: true,
|
||||||
|
isActive: true,
|
||||||
|
}] as SystemMenuItem[];
|
||||||
|
const tree = [{
|
||||||
|
id: "dashboard",
|
||||||
|
name: "仪表盘",
|
||||||
|
type: "page",
|
||||||
|
parentId: null,
|
||||||
|
sortOrder: 10,
|
||||||
|
children: [],
|
||||||
|
}] as unknown as SystemMenuTreeItem[];
|
||||||
|
|
||||||
|
expect(hydrateMenuTree(tree, flatItems)[0]).toMatchObject({ isVisible: true, isActive: true });
|
||||||
|
});
|
||||||
|
|
||||||
it("uses RuoYi-compatible Chinese labels for data scope", () => {
|
it("uses RuoYi-compatible Chinese labels for data scope", () => {
|
||||||
expect(dataScopeLabel("all")).toBe("全部数据");
|
expect(dataScopeLabel("all")).toBe("全部数据");
|
||||||
expect(dataScopeLabel("dept_and_children")).toBe("本部门及以下");
|
expect(dataScopeLabel("dept_and_children")).toBe("本部门及以下");
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { ref, watch, type Ref } from "vue";
|
import { ref, watch, type Ref } from "vue";
|
||||||
import { getSystemResource } from "@/api/client";
|
import { getSystemResource } from "@/api/client";
|
||||||
import type { SystemRoleItem } from "@/types";
|
import type { SystemMenuItem, SystemMenuTreeItem, SystemRoleItem } from "@/types";
|
||||||
|
|
||||||
export type SystemResourceResponse = { items: unknown[] };
|
export type SystemResourceResponse = { items: unknown[] };
|
||||||
export type SystemResourceFetcher = (resource: string) => Promise<SystemResourceResponse>;
|
export type SystemResourceFetcher = (resource: string) => Promise<SystemResourceResponse>;
|
||||||
@@ -25,6 +25,18 @@ export function buildSystemTree<T extends TreeSource>(items: T[]): Array<SystemT
|
|||||||
return roots;
|
return roots;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function hydrateMenuTree(tree: SystemMenuTreeItem[], items: readonly SystemMenuItem[]): SystemMenuTreeItem[] {
|
||||||
|
const itemMap = new Map(items.map((item) => [item.id, item]));
|
||||||
|
const hydrate = (nodes: SystemMenuTreeItem[]): SystemMenuTreeItem[] => nodes.map((node) => {
|
||||||
|
const item = itemMap.get(node.id);
|
||||||
|
return {
|
||||||
|
...(item ? { ...item, ...node } : node),
|
||||||
|
children: hydrate(node.children ?? []),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return hydrate(tree);
|
||||||
|
}
|
||||||
|
|
||||||
export function filterSystemTree<T extends TreeSource>(nodes: Array<SystemTreeNode<T>>, keyword: string): Array<SystemTreeNode<T>> {
|
export function filterSystemTree<T extends TreeSource>(nodes: Array<SystemTreeNode<T>>, keyword: string): Array<SystemTreeNode<T>> {
|
||||||
const normalized = keyword.trim().toLowerCase();
|
const normalized = keyword.trim().toLowerCase();
|
||||||
if (!normalized) return nodes;
|
if (!normalized) return nodes;
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import {
|
|||||||
import LayoutIcon from "@/components/layout/LayoutIcon.vue";
|
import LayoutIcon from "@/components/layout/LayoutIcon.vue";
|
||||||
import SystemResourceEditor, { type SystemEditorSavePayload, type SystemResourceName } from "@/components/system/SystemResourceEditor.vue";
|
import SystemResourceEditor, { type SystemEditorSavePayload, type SystemResourceName } from "@/components/system/SystemResourceEditor.vue";
|
||||||
import { getMenuIconName } from "@/lib/menu-display";
|
import { getMenuIconName } from "@/lib/menu-display";
|
||||||
import { buildSystemTree, dataScopeLabel, filterSystemTree } from "@/lib/system-resource";
|
import { buildSystemTree, dataScopeLabel, filterSystemTree, hydrateMenuTree } from "@/lib/system-resource";
|
||||||
import type {
|
import type {
|
||||||
SystemDeptCreate,
|
SystemDeptCreate,
|
||||||
SystemDeptItem,
|
SystemDeptItem,
|
||||||
@@ -144,7 +144,8 @@ async function loadResource() {
|
|||||||
if (resource.value === "menus") {
|
if (resource.value === "menus") {
|
||||||
const result = await getSystemMenus();
|
const result = await getSystemMenus();
|
||||||
menuItems.value = result.items;
|
menuItems.value = result.items;
|
||||||
menuTree.value = result.tree?.length ? result.tree : buildMenuTree(result.items);
|
const responseTree = result.tree?.length ? result.tree : buildMenuTree(result.items);
|
||||||
|
menuTree.value = hydrateMenuTree(responseTree, result.items);
|
||||||
items.value = result.items;
|
items.value = result.items;
|
||||||
}
|
}
|
||||||
if (resource.value === "depts") {
|
if (resource.value === "depts") {
|
||||||
@@ -412,7 +413,7 @@ onMounted(loadResource);
|
|||||||
<el-table-column prop="path" label="路由路径" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="path" label="路由路径" min-width="150" show-overflow-tooltip />
|
||||||
<el-table-column prop="permission" label="权限标识" min-width="170" show-overflow-tooltip />
|
<el-table-column prop="permission" label="权限标识" min-width="170" show-overflow-tooltip />
|
||||||
<el-table-column prop="component" label="组件路径" min-width="150" show-overflow-tooltip />
|
<el-table-column prop="component" label="组件路径" min-width="150" show-overflow-tooltip />
|
||||||
<el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="row.isActive && row.isVisible ? 'success' : 'info'" effect="plain">{{ row.isActive && row.isVisible ? '正常' : '停用' }}</el-tag></template></el-table-column>
|
<el-table-column label="状态" width="90"><template #default="{ row }"><el-tag :type="row.isActive ? 'success' : 'info'" effect="plain">{{ row.isActive ? '启用' : '停用' }}</el-tag></template></el-table-column>
|
||||||
<el-table-column label="操作" width="230" fixed="right"><template #default="{ row }"><el-button v-permission="updatePermission" link type="primary" @click="openEdit(row)">修改</el-button><el-button v-if="row.type !== 'button'" v-permission="createPermission" link type="primary" @click="openCreate(row.id)">新增</el-button><el-button v-permission="deletePermission" link type="danger" :loading="deletingId === row.id" @click="removeItem(row)">删除</el-button></template></el-table-column>
|
<el-table-column label="操作" width="230" fixed="right"><template #default="{ row }"><el-button v-permission="updatePermission" link type="primary" @click="openEdit(row)">修改</el-button><el-button v-if="row.type !== 'button'" v-permission="createPermission" link type="primary" @click="openCreate(row.id)">新增</el-button><el-button v-permission="deletePermission" link type="danger" :loading="deletingId === row.id" @click="removeItem(row)">删除</el-button></template></el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user