feat: 导航/侧边栏菜单脱敏 + 圆桌会商各步输入框自适应高度

- 菜单标签展示层脱敏:useSidebarMenuItems 直接用 /api/sensitive-words
  结果构造脱敏器,覆盖导航栏/三套侧边栏皮肤/三级子导航;菜单管理页用原始树不受影响
- 新增可复用 AutoGrowTextarea(自适应高度多行框),替换圆桌 Step1/2/3 输入框:
  多行时自动扩展、到上限框内滚动;Step2 两个单行 input 改多行;统一 Enter 发送/Shift+Enter 换行
- 各步输入框最大展开高度翻倍(Step1 320 / Step2 280 / Step3 224)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
董宏凯andClaude Opus 4.8 committed 2026-06-17 19:13:48 +08:00
1 parent 25e4e3dbc9
commit 6746fe4a78
9 files changed
+141 -29

No files matched your search

@@ -12,8 +12,10 @@ import { useLocation } from "react-router-dom";
import { Layout } from "lucide-react";
import { getStoredAuth } from "@/core/auth";
import { desensitize, makeDesensitizer } from "@/lib/desensitize";
import { useLightApps, type LightApp } from "@/strategy-components/api/light-apps";
import { useMenuOverrides } from "@/strategy-components/api/menu-overrides";
import { useSensitiveWords, wordsToMap } from "@/strategy-components/api/sensitive-words";
import { applyMenuOverrides } from "./menu-overrides";
import {
@@ -58,23 +60,43 @@ export function injectLightApps(items: MenuItem[], apps: LightApp[]): MenuItem[]
return walk(items);
}
/** 递归对菜单标签做脱敏(仅改 `label`,path/id/icon 不动,保证高亮与路由不受影响)。 */
function desensitizeLabels(items: MenuItem[], fn: (text: string) => string): MenuItem[] {
return items.map((node) => ({
...node,
label: fn(node.label),
children: node.children ? desensitizeLabels(node.children, fn) : node.children,
}));
}
/**
* 返回合并了 iframe 轻应用、套用了后端「菜单覆盖层」(改名/停用/排序/层级)、
* 并按 admin 角色过滤后的菜单。
* 按 admin 角色过滤、并对标签做**展示层脱敏**后的菜单。
*
* 管线顺序:静态菜单 → 注入 iframe 轻应用 → 套覆盖层(停用项被剔除)→ 角色过滤。
* 管线顺序:静态菜单 → 注入 iframe 轻应用 → 套覆盖层(停用项被剔除)→ 角色过滤 → 标签脱敏。
* 覆盖层作用在合并后的整棵树上,所以 iframe 轻应用节点同样可被改名/停用/移层级。
*
* 脱敏直接用 `/api/sensitive-words` 查询结果构造(后台非空时用其映射,否则回退到内置种子的
* 全局 {@link desensitize}),避免依赖 {@link replaceDesensitizeMap} 的 effect 回灌时序。
*/
export function useSidebarMenuItems(isAdmin: boolean): MenuItem[] {
const { data: iframeApps = [] } = useLightApps({ appType: "iframe", enabledOnly: true });
const { data: overrides = [] } = useMenuOverrides();
const { data: words = [] } = useSensitiveWords();
const desensitizeLabel = useMemo(
() => (words.length > 0 ? makeDesensitizer(wordsToMap(words)) : desensitize),
[words],
);
return useMemo(
() =>
filterMenuItemsByRole(
applyMenuOverrides(injectLightApps(sidebarMenuItems, iframeApps), overrides),
isAdmin,
desensitizeLabels(
filterMenuItemsByRole(
applyMenuOverrides(injectLightApps(sidebarMenuItems, iframeApps), overrides),
isAdmin,
),
desensitizeLabel,
),
[iframeApps, overrides, isAdmin],
[iframeApps, overrides, isAdmin, desensitizeLabel],
);
}