feat: 业务链条配置管理导航与顶栏用户名统一
业务链条页增加配置管理入口与返回导航;配置管理页通过 aliasPaths 保持侧栏选中;顶栏用户名与设置个人信息页共用 getAccountDisplayName。
This commit is contained in:
1 parent
d8102e8db8
commit
318f6c0c0c
8 files changed
+101
-64
No files matched your search
@@ -6,6 +6,8 @@ import { getStoredAuth } from "@/core/auth";
|
|||||||
import { isWujieEnv } from "@/core/embed/wujie";
|
import { isWujieEnv } from "@/core/embed/wujie";
|
||||||
import {
|
import {
|
||||||
defaultExpandedIds,
|
defaultExpandedIds,
|
||||||
|
menuItemContainsPath,
|
||||||
|
menuItemMatchesPath,
|
||||||
type MenuItem,
|
type MenuItem,
|
||||||
} from "@/core/page-layout/sidebar-menu";
|
} from "@/core/page-layout/sidebar-menu";
|
||||||
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
|
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
|
||||||
@@ -53,13 +55,7 @@ export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDar
|
|||||||
}, 180);
|
}, 180);
|
||||||
};
|
};
|
||||||
|
|
||||||
const hasActiveChild = (item: MenuItem): boolean => {
|
const hasActiveChild = (item: MenuItem): boolean => menuItemContainsPath(item, pathname);
|
||||||
if (item.path && (pathname === item.path || pathname.startsWith(item.path + "/"))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return item.children?.some(hasActiveChild) ?? false;
|
|
||||||
};
|
|
||||||
|
|
||||||
// lazy init:仅在首次 render 计算一次 active id;之后由下面的 useEffect 跟随 pathname 同步
|
// lazy init:仅在首次 render 计算一次 active id;之后由下面的 useEffect 跟随 pathname 同步
|
||||||
const [activeTopId, setActiveTopId] = useState<string>(
|
const [activeTopId, setActiveTopId] = useState<string>(
|
||||||
@@ -152,9 +148,9 @@ export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDar
|
|||||||
const isSidebarItemActive = (item: MenuItem) =>
|
const isSidebarItemActive = (item: MenuItem) =>
|
||||||
inWujieEnv
|
inWujieEnv
|
||||||
? item.id === activeTopId
|
? item.id === activeTopId
|
||||||
: item.path
|
: item.path || item.aliasPaths?.length
|
||||||
? pathname === item.path || pathname.startsWith(item.path + "/")
|
? menuItemMatchesPath(item, pathname)
|
||||||
: hasActiveChild(item);
|
: menuItemContainsPath(item, pathname);
|
||||||
const handleSidebarItemClick = (item: MenuItem) => {
|
const handleSidebarItemClick = (item: MenuItem) => {
|
||||||
if (inWujieEnv) {
|
if (inWujieEnv) {
|
||||||
handleTopItemClick(item);
|
handleTopItemClick(item);
|
||||||
@@ -176,7 +172,9 @@ export function PageSidebarDark({ collapsed, onToggleCollapsed }: PageSidebarDar
|
|||||||
{items.map((item) => {
|
{items.map((item) => {
|
||||||
const hasChildren = !!item.children?.length;
|
const hasChildren = !!item.children?.length;
|
||||||
const isExpanded = expandedIds.has(item.id);
|
const isExpanded = expandedIds.has(item.id);
|
||||||
const isSelected = item.path ? pathname === item.path : hasActiveChild(item);
|
const isSelected = item.path || item.aliasPaths?.length
|
||||||
|
? menuItemMatchesPath(item, pathname)
|
||||||
|
: hasActiveChild(item);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ import { useUnreadCount } from "@/core/notifications";
|
|||||||
import {
|
import {
|
||||||
defaultExpandedIds,
|
defaultExpandedIds,
|
||||||
getActiveThirdLevelItems,
|
getActiveThirdLevelItems,
|
||||||
|
menuItemContainsPath,
|
||||||
|
menuItemMatchesPath,
|
||||||
type MenuItem,
|
type MenuItem,
|
||||||
} from "@/core/page-layout/sidebar-menu";
|
} from "@/core/page-layout/sidebar-menu";
|
||||||
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
|
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
|
||||||
@@ -41,12 +43,8 @@ type PageSidebarProps = {
|
|||||||
|
|
||||||
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about";
|
type SettingsSection = "account" | "appearance" | "memory" | "tools" | "about";
|
||||||
|
|
||||||
const hasActiveChild = (item: MenuItem, pathname: string): boolean => {
|
const hasActiveChild = (item: MenuItem, pathname: string): boolean =>
|
||||||
if (item.path && (pathname === item.path || pathname.startsWith(item.path + "/"))) {
|
menuItemContainsPath(item, pathname);
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return item.children?.some((child) => hasActiveChild(child, pathname)) ?? false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const findFirstRoutePath = (items?: MenuItem[]): string | null => {
|
const findFirstRoutePath = (items?: MenuItem[]): string | null => {
|
||||||
if (!items?.length) {
|
if (!items?.length) {
|
||||||
@@ -116,9 +114,9 @@ function SecondaryNavSidebar() {
|
|||||||
const secondaryItems = activeTopItem?.children ?? [];
|
const secondaryItems = activeTopItem?.children ?? [];
|
||||||
|
|
||||||
const isItemActive = (item: MenuItem) =>
|
const isItemActive = (item: MenuItem) =>
|
||||||
item.path
|
item.path || item.aliasPaths?.length
|
||||||
? pathname === item.path || pathname.startsWith(item.path + "/")
|
? menuItemMatchesPath(item, pathname)
|
||||||
: hasActiveChild(item, pathname);
|
: menuItemContainsPath(item, pathname);
|
||||||
|
|
||||||
const handleItemClick = (item: MenuItem) => {
|
const handleItemClick = (item: MenuItem) => {
|
||||||
const path = item.path ?? findFirstRoutePath(item.children);
|
const path = item.path ?? findFirstRoutePath(item.children);
|
||||||
@@ -211,9 +209,8 @@ function SecondaryNavSidebar() {
|
|||||||
<nav className="light-sidebar-subnav" aria-label="三级菜单">
|
<nav className="light-sidebar-subnav" aria-label="三级菜单">
|
||||||
{thirdItems.map((child) => {
|
{thirdItems.map((child) => {
|
||||||
const ChildIcon = child.icon;
|
const ChildIcon = child.icon;
|
||||||
const childActive = child.path
|
const childActive = menuItemMatchesPath(child, pathname)
|
||||||
? pathname === child.path || pathname.startsWith(child.path + "/")
|
|| (child.children?.length ? menuItemContainsPath(child, pathname) : false);
|
||||||
: hasActiveChild(child, pathname);
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={child.id}
|
key={child.id}
|
||||||
@@ -301,7 +298,7 @@ function LegacyAccordionSidebar({
|
|||||||
|
|
||||||
const renderLeaf = (item: MenuItem, depth = 0) => {
|
const renderLeaf = (item: MenuItem, depth = 0) => {
|
||||||
const Icon = item.icon;
|
const Icon = item.icon;
|
||||||
const isActive = !!item.path && pathname === item.path;
|
const isActive = menuItemMatchesPath(item, pathname);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { toast } from "sonner";
|
|||||||
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { clearStoredAuth, getStoredAuth } from "@/core/auth";
|
import { clearStoredAuth, getAccountDisplayName, getStoredAuth } from "@/core/auth";
|
||||||
|
|
||||||
import { SettingsSection } from "./settings-section";
|
import { SettingsSection } from "./settings-section";
|
||||||
|
|
||||||
@@ -37,8 +37,7 @@ export function AccountSettingsPage() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const usernameFromEmail = (auth.email ?? "").split("@")[0] ?? "";
|
const displayName = getAccountDisplayName(auth);
|
||||||
const displayName = usernameFromEmail || auth.user_id;
|
|
||||||
|
|
||||||
const handleLogout = () => {
|
const handleLogout = () => {
|
||||||
clearStoredAuth();
|
clearStoredAuth();
|
||||||
|
|||||||
@@ -44,8 +44,17 @@ export function clearStoredAuth() {
|
|||||||
window.localStorage.removeItem(AUTH_STORAGE_KEY);
|
window.localStorage.removeItem(AUTH_STORAGE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 与设置 → 个人信息页「用户名」一致:邮箱 @ 前缀,否则 user_id。 */
|
||||||
|
export function getAccountDisplayName(
|
||||||
|
auth: LoginByUsernameResponse | null = getStoredAuth(),
|
||||||
|
): string {
|
||||||
|
if (!auth) return "用户";
|
||||||
|
const usernameFromEmail = (auth.email ?? "").split("@")[0] ?? "";
|
||||||
|
return usernameFromEmail || auth.user_id || "用户";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 把登录用户名写入 localStorage.userInfo.userName(顶栏用户区据此显示)。
|
* 把登录用户名写入 localStorage.userInfo.userName(轻应用登录参数等场景使用)。
|
||||||
* 仅在尚未设置时写入,避免覆盖外部登录(yUserId)从 consumer 拿到的用户名。
|
* 仅在尚未设置时写入,避免覆盖外部登录(yUserId)从 consumer 拿到的用户名。
|
||||||
*/
|
*/
|
||||||
export function setStoredUserName(name: string | null | undefined) {
|
export function setStoredUserName(name: string | null | undefined) {
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ export type MenuItem = {
|
|||||||
label: string;
|
label: string;
|
||||||
icon: ElementType;
|
icon: ElementType;
|
||||||
path?: string;
|
path?: string;
|
||||||
|
/** 额外视为「当前页属于本菜单项」的路由(不在侧栏展示,仅用于高亮)。 */
|
||||||
|
aliasPaths?: string[];
|
||||||
/** 非 admin 用户使用的路由(菜单对普通用户可见时生效,与简洁模式公开排行榜一致) */
|
/** 非 admin 用户使用的路由(菜单对普通用户可见时生效,与简洁模式公开排行榜一致) */
|
||||||
userPath?: string;
|
userPath?: string;
|
||||||
adminOnly?: boolean;
|
adminOnly?: boolean;
|
||||||
@@ -219,6 +221,7 @@ export const sidebarMenuItems: MenuItem[] = [
|
|||||||
label: "业务链条配置",
|
label: "业务链条配置",
|
||||||
icon: Layers,
|
icon: Layers,
|
||||||
path: "/page/strategy/business-chains",
|
path: "/page/strategy/business-chains",
|
||||||
|
aliasPaths: ["/page/strategy/business-chain-mapping"],
|
||||||
},
|
},
|
||||||
{ id: "mgmt-skills", label: "技能管理", icon: Sparkles, path: "/page/strategy/skills" },
|
{ id: "mgmt-skills", label: "技能管理", icon: Sparkles, path: "/page/strategy/skills" },
|
||||||
{ id: "mgmt-memory", label: "记忆管理", icon: Brain, path: "/page/strategy/memory" },
|
{ id: "mgmt-memory", label: "记忆管理", icon: Brain, path: "/page/strategy/memory" },
|
||||||
@@ -308,11 +311,21 @@ export const defaultExpandedIds: ReadonlySet<string> = new Set([
|
|||||||
"comprehensive-management",
|
"comprehensive-management",
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
/** 单条路由是否与 pathname 精确匹配或为其子路径。 */
|
||||||
|
function routeMatchesPathname(pathname: string, route?: string): boolean {
|
||||||
|
if (!route) return false;
|
||||||
|
return pathname === route || pathname.startsWith(route + "/");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 菜单项自身 path / aliasPaths 是否匹配当前路由。 */
|
||||||
|
export function menuItemMatchesPath(item: MenuItem, pathname: string): boolean {
|
||||||
|
if (routeMatchesPathname(pathname, item.path)) return true;
|
||||||
|
return item.aliasPaths?.some((p) => routeMatchesPathname(pathname, p)) ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
/** 该菜单项自身或任意后代的 path 是否匹配当前路由(支持子路径,如 /knowledge/:id) */
|
/** 该菜单项自身或任意后代的 path 是否匹配当前路由(支持子路径,如 /knowledge/:id) */
|
||||||
export function menuItemContainsPath(item: MenuItem, pathname: string): boolean {
|
export function menuItemContainsPath(item: MenuItem, pathname: string): boolean {
|
||||||
if (item.path && (pathname === item.path || pathname.startsWith(item.path + "/"))) {
|
if (menuItemMatchesPath(item, pathname)) return true;
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return item.children?.some((child) => menuItemContainsPath(child, pathname)) ?? false;
|
return item.children?.some((child) => menuItemContainsPath(child, pathname)) ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { motion } from "motion/react";
|
|||||||
import { Select } from "tdesign-react";
|
import { Select } from "tdesign-react";
|
||||||
import {
|
import {
|
||||||
AlertCircle,
|
AlertCircle,
|
||||||
|
ArrowLeft,
|
||||||
HelpCircle,
|
HelpCircle,
|
||||||
Info,
|
Info,
|
||||||
Link2,
|
Link2,
|
||||||
@@ -21,6 +22,7 @@ import {
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { useTheme } from "@/contexts/ThemeContext";
|
import { useTheme } from "@/contexts/ThemeContext";
|
||||||
|
import { useRouteNavigate } from "@/core/route-base";
|
||||||
import { listAgents } from "@/core/agents/api";
|
import { listAgents } from "@/core/agents/api";
|
||||||
import type { Agent } from "@/core/agents/types";
|
import type { Agent } from "@/core/agents/types";
|
||||||
import { listChains } from "@/roundtable-planning/api/chains";
|
import { listChains } from "@/roundtable-planning/api/chains";
|
||||||
@@ -34,6 +36,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import { useDesensitizeSubtree } from "@/roundtable-planning/lib/use-desensitize-subtree";
|
import { useDesensitizeSubtree } from "@/roundtable-planning/lib/use-desensitize-subtree";
|
||||||
|
|
||||||
export default function BusinessChainMappingPage() {
|
export default function BusinessChainMappingPage() {
|
||||||
|
const navigate = useRouteNavigate();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
const isDark = theme === "dark";
|
const isDark = theme === "dark";
|
||||||
// 整片脱敏:业务链条映射页展示文本(链条名/描述/席位名/标题)。输入框 Hook 自动跳过。
|
// 整片脱敏:业务链条映射页展示文本(链条名/描述/席位名/标题)。输入框 Hook 自动跳过。
|
||||||
@@ -233,6 +236,20 @@ export default function BusinessChainMappingPage() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => navigate("/page/strategy/business-chains")}
|
||||||
|
className={cn(
|
||||||
|
"relative z-10 inline-flex shrink-0 items-center gap-1.5 rounded-[5px] border px-3.5 py-2 text-xs font-semibold transition-all active:scale-95",
|
||||||
|
isDark
|
||||||
|
? "border-white/10 bg-white/5 text-slate-300 hover:bg-white/10 hover:text-slate-100"
|
||||||
|
: "border-slate-200 bg-white text-slate-600 hover:bg-slate-50 hover:text-slate-900",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<ArrowLeft className="size-3.5" />
|
||||||
|
返回业务链条
|
||||||
|
</button>
|
||||||
</motion.div>
|
</motion.div>
|
||||||
|
|
||||||
{/* ── 表格区 ── */}
|
{/* ── 表格区 ── */}
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ import {
|
|||||||
SearchIcon,
|
SearchIcon,
|
||||||
SendIcon,
|
SendIcon,
|
||||||
SettingsIcon,
|
SettingsIcon,
|
||||||
|
SlidersHorizontalIcon,
|
||||||
Trash2Icon,
|
Trash2Icon,
|
||||||
UserIcon,
|
UserIcon,
|
||||||
UsersIcon,
|
UsersIcon,
|
||||||
@@ -252,17 +253,27 @@ export default function BusinessChainListPage() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button
|
<div className="flex shrink-0 items-center gap-2 self-start md:self-auto">
|
||||||
type="button"
|
<button
|
||||||
onClick={() => goConfigure()}
|
type="button"
|
||||||
className={cn(
|
onClick={() => navigate("/page/strategy/business-chain-mapping")}
|
||||||
"flex shrink-0 items-center gap-2.5 self-start rounded-xl px-4 py-2.5 text-xs font-bold shadow-sm shadow-[#CF1322]/10 transition active:scale-95 md:self-auto",
|
className="flex items-center gap-2 rounded-xl border border-zinc-200 bg-white px-4 py-2.5 text-xs font-bold text-zinc-700 shadow-sm transition hover:bg-zinc-50 active:scale-95 dark:border-[#1A2F5A] dark:bg-[#122347] dark:text-[#C5CFE0] dark:hover:bg-[#1A2F5A]"
|
||||||
BRAND,
|
>
|
||||||
)}
|
<SlidersHorizontalIcon className="size-4" />
|
||||||
>
|
<span>配置管理</span>
|
||||||
<PlusIcon className="size-4" />
|
</button>
|
||||||
<span>新建链条</span>
|
<button
|
||||||
</button>
|
type="button"
|
||||||
|
onClick={() => goConfigure()}
|
||||||
|
className={cn(
|
||||||
|
"flex items-center gap-2.5 rounded-xl px-4 py-2.5 text-xs font-bold shadow-sm shadow-[#CF1322]/10 transition active:scale-95",
|
||||||
|
BRAND,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<PlusIcon className="size-4" />
|
||||||
|
<span>新建链条</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -1,23 +1,24 @@
|
|||||||
import { Bell, ChevronDown, FileText, Shirt, User } from "lucide-react";
|
import { Bell, ChevronDown, FileText, Shirt, User } from "lucide-react";
|
||||||
import { useEffect, useMemo, useRef, useState } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
import { useLocation, useNavigate } from "react-router-dom";
|
import { useLocation, useNavigate } from "react-router-dom";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import "@/components/page-sdebar.css";
|
import "@/components/page-sdebar.css";
|
||||||
import { NotificationDialog } from "@/components/workspace/notification-dialog";
|
import { NotificationDialog } from "@/components/workspace/notification-dialog";
|
||||||
import { getStoredAuth } from "@/core/auth";
|
import { getStoredAuth, getAccountDisplayName } from "@/core/auth";
|
||||||
import { isWujieEnv } from "@/core/embed/wujie";
|
import { isWujieEnv } from "@/core/embed/wujie";
|
||||||
import { useUnreadCount } from "@/core/notifications";
|
import { useUnreadCount } from "@/core/notifications";
|
||||||
import { useMyPosition } from "@/core/positions";
|
import { useMyPosition } from "@/core/positions";
|
||||||
import {
|
import {
|
||||||
getActiveThirdLevelItems,
|
getActiveThirdLevelItems,
|
||||||
|
menuItemContainsPath,
|
||||||
|
menuItemMatchesPath,
|
||||||
type MenuItem,
|
type MenuItem,
|
||||||
} from "@/core/page-layout/sidebar-menu";
|
} from "@/core/page-layout/sidebar-menu";
|
||||||
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
|
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
import { useTheme, type ThemeType } from "../contexts/ThemeContext";
|
import { useTheme, type ThemeType } from "../contexts/ThemeContext";
|
||||||
import { readUserInfo } from "./lib/light-app";
|
|
||||||
|
|
||||||
const iconButtonClass = (isDark: boolean) =>
|
const iconButtonClass = (isDark: boolean) =>
|
||||||
cn(
|
cn(
|
||||||
@@ -135,28 +136,14 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
const { data: myPosition } = useMyPosition();
|
const { data: myPosition } = useMyPosition();
|
||||||
const positionName = myPosition?.name?.trim() || "";
|
const positionName = myPosition?.name?.trim() || "";
|
||||||
|
|
||||||
// 登录时写入的真实用户名(外部登录存于 localStorage.userInfo.userName);
|
const displayName = getAccountDisplayName(auth);
|
||||||
// 缺省回退到 stored auth 的邮箱前缀。
|
|
||||||
const displayName = useMemo(() => {
|
|
||||||
const info = readUserInfo();
|
|
||||||
const name = typeof info.userName === "string" ? info.userName.trim() : "";
|
|
||||||
if (name) return name;
|
|
||||||
const email = auth?.email?.trim();
|
|
||||||
return email ? email.split("@")[0] : "用户";
|
|
||||||
}, [auth?.email]);
|
|
||||||
const showTopNav = isDark && !collapsed && !isWujieEnv();
|
const showTopNav = isDark && !collapsed && !isWujieEnv();
|
||||||
// 浅色(红/蓝)顶栏:一级导航正常横排进顶栏,不做深色那种围绕基座的特殊处理。
|
// 浅色(红/蓝)顶栏:一级导航正常横排进顶栏,不做深色那种围绕基座的特殊处理。
|
||||||
// 与 collapsed 解耦(折叠只影响侧边栏,一级导航始终可见);wujie 嵌入下隐藏,
|
// 与 collapsed 解耦(折叠只影响侧边栏,一级导航始终可见);wujie 嵌入下隐藏,
|
||||||
// 此时一级导航回落到侧边栏(见 page-sidebar.tsx)。
|
// 此时一级导航回落到侧边栏(见 page-sidebar.tsx)。
|
||||||
const showLightTopNav = !isDark && !isWujieEnv();
|
const showLightTopNav = !isDark && !isWujieEnv();
|
||||||
|
|
||||||
const hasActiveChild = (item: MenuItem): boolean => {
|
const hasActiveChild = (item: MenuItem): boolean => menuItemContainsPath(item, pathname);
|
||||||
if (item.path && (pathname === item.path || pathname.startsWith(item.path + "/"))) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return item.children?.some(hasActiveChild) ?? false;
|
|
||||||
};
|
|
||||||
|
|
||||||
const findFirstRoutePath = (items?: MenuItem[]): string | null => {
|
const findFirstRoutePath = (items?: MenuItem[]): string | null => {
|
||||||
if (!items?.length) {
|
if (!items?.length) {
|
||||||
@@ -189,7 +176,9 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderTopItem = (item: MenuItem) => {
|
const renderTopItem = (item: MenuItem) => {
|
||||||
const isSelected = item.path ? (pathname === item.path || pathname.startsWith(item.path + "/")) : hasActiveChild(item);
|
const isSelected = item.path || item.aliasPaths?.length
|
||||||
|
? menuItemMatchesPath(item, pathname)
|
||||||
|
: hasActiveChild(item);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -203,7 +192,9 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const renderLightTopItem = (item: MenuItem) => {
|
const renderLightTopItem = (item: MenuItem) => {
|
||||||
const isSelected = item.path ? (pathname === item.path || pathname.startsWith(item.path + "/")) : hasActiveChild(item);
|
const isSelected = item.path || item.aliasPaths?.length
|
||||||
|
? menuItemMatchesPath(item, pathname)
|
||||||
|
: hasActiveChild(item);
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
key={item.id}
|
key={item.id}
|
||||||
@@ -388,7 +379,9 @@ const Header = ({ collapsed }: HeaderProps) => {
|
|||||||
<nav className="third-submenu-bar" aria-label="三级菜单">
|
<nav className="third-submenu-bar" aria-label="三级菜单">
|
||||||
<div className="third-submenu-list">
|
<div className="third-submenu-list">
|
||||||
{thirdSubNavItems.map((item) => {
|
{thirdSubNavItems.map((item) => {
|
||||||
const isSelected = item.path ? (pathname === item.path || pathname.startsWith(item.path + "/")) : hasActiveChild(item);
|
const isSelected = item.path || item.aliasPaths?.length
|
||||||
|
? menuItemMatchesPath(item, pathname)
|
||||||
|
: hasActiveChild(item);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
|
|||||||
Reference in new issue
Block a user