You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

前端导航权限优化:如何根据后端权限控制导航项显示?

导航栏权限控制优化问题

我有一个导航栏组件,后端返回的权限集合存储在全局上下文的user对象中,可通过user.permissions获取,结构如下:

{addCustomer: false, editAndDeleteCustomer: false, showAllCustomers: false, addEmployee: false, editAndDeleteEmployee: false, …}

现在需要优化我的NavLinks组件,使其仅显示权限值为true对应的菜单项。例如当addCustomer为false时,“添加客户”菜单项不应出现在菜单列表里。

以下是我的实现代码:

const NavLinks = ({ ToggleSideBar }) => {
  const { user } = useAppContext();

  const links = [
    {
      id: 1,
      text: "panel",
      path: "/",
      icon: <IoBarChartSharp />,
      menuItems: ["panel"],
    },
    {
      id: 2,
      text: "employee",
      path: ["add-employee", "all-employee"],
      icon: <MdQueryStats />,
      menuItems: [
        "add new employee",
        "all employees",
      ],
    },
    {
      id: 3,
      text: "customer",
      path: ["add-customer", "emp-customers", "all-customers"],
      icon: <FaWpforms />,
      menuItems: [
        "add new customer",
        "my customers",
        "all customers",
      ],
    },
    {
      id: 4,
      text: "التقارير",
      path: ["profile", "aw", "all-jobs", "add-job"],
      icon: <ImProfile />,
      menuItems: [
        "add customer",
        "customer Roles",
        "all customers",
        "الموظف المثالي",
      ],
    },
    
  ];
 
  const [isDropdownOpen2, setIsDropdownOpen2] = useState(false);
  const [isDropdownOpen3, setIsDropdownOpen3] = useState(false);

  const toggleDropdown2 = () => {
    setIsDropdownOpen2(!isDropdownOpen2);
  };
  const toggleDropdown3 = () => {
    setIsDropdownOpen3(!isDropdownOpen3);
  };

  const [activeStatus, setActiveStatus] = useState({
    firstnavcontainer: false,
    secondnavcontainer: false,
    thirdnavcontainer: false,
  });

  const firstFunction = () => {
    setActiveStatus((prevState) => ({
      firstnavcontainer: true,
      secondnavcontainer: false,
      thirdnavcontainer: false,
    }));
  };
  const secondFunction = () => {
    setActiveStatus((prevState) => ({
      ...prevState,
      firstnavcontainer: false,
      secondnavcontainer: true,
      thirdnavcontainer: false,
    }));
    console.log(activeStatus);
  };
  const thirdFunction = () => {
    setActiveStatus((prevState) => ({
      ...prevState,
      firstnavcontainer: false,
      secondnavcontainer: false,
      thirdnavcontainer: true,
    }));
  };
  return (
    <div className="nav-links">
      {links.map((link) => {
        const { text, path, id, icon, menuItems } = link;
        return (
          <>
            {/* first item */}
            {id === 1 && (
              <div className="nav-item extra" onClick={firstFunction}>
                <ul className="dropdown-menu">
                  {menuItems.map((item, index) => (
                    <NavLink
                      to={path[index]}
                      className={({ isActive }) =>
                        isActive
                          ? "dropdownmenu-item red-background"
                          : "dropdownmenu-item"
                      }
                    >
                      <div className="dropdown-itemcontainer">
                        <span className="dropdown-itemtext">{item}</span>
                      </div>
                    </NavLink>
                  ))}
                </ul>
              </div>
            )}
            {/* second section */}
            {id === 2 && (
              <div className="nav-item extra" onClick={secondFunction}>
                <button
                  className={`dropdown-toggle ${
                    activeStatus.secondnavcontainer ? "red-background" : ""
                  }`}
                >
                  <div className="dropdownbtn">
                    <span className="icon">{icon}</span>
                    <span>{text}</span>
                  </div>
                  <BsChevronDown
                    className="dropdown-icon"
                    onClick={toggleDropdown2}
                  />
                </button>
                {isDropdownOpen2 && (
                  <ul className="dropdown-menu">
                    {menuItems.map((item, index) => (
                      <NavLink
                        to={path[index]}
                        className={({ isActive }) =>
                          isActive
                            ? "dropdownmenu-item active"
                            : "dropdownmenu-item"
                        }
                      >
                        <div className="dropdown-itemcontainer">
                          <span className="dropdown-itemtext">{item}</span>
                        </div>
                      </NavLink>
                    ))}
                  </ul>
                )}
              </div>
            )}
            {/* third section */}
            {id === 3 && (
              <div className="nav-item extra" onClick={thirdFunction}>
                <button
                  className={`dropdown-toggle ${
                    activeStatus.thirdnavcontainer ? "red-background" : ""
                  }`}
                >
                  <div className="dropdownbtn">
                    <span className="icon">{icon}</span>
                    <span>{text}</span>
                  </div>
                  <BsChevronDown
                    className="dropdown-icon"
                    onClick={toggleDropdown3}
                  />
                </button>
                {isDropdownOpen3 && (
                  <ul className="dropdown-menu">
                    {menuItems.map((item, index) => (
                      <NavLink
                        to={path[index]}
                        className={({ isActive }) =>
                          isActive
                            ? "dropdownmenu-item active"
                            : "dropdownmenu-item"
                        }
                      >
                        <div className="dropdown-itemcontainer">
                          <span className="dropdown-itemtext">{item}</span>
                        </div>
                      </NavLink>
                    ))}
                  </ul>
                )}
              </div>
            )}
          </>
        );
      })}
    </div>
  );
};

export default NavLinks;

想请教如何实现权限过滤,另外有没有更优的权限控制方案?


解决方案与优化思路

一、基础实现:绑定权限并过滤菜单项

首先修改links结构,给每个子菜单项关联对应的权限字段,让权限和菜单配置一一对应:

const links = [
  {
    id: 1,
    text: "panel",
    path: "/",
    icon: <IoBarChartSharp />,
    subItems: [{ label: "panel", permission: null }], // 无需权限的项设为null
  },
  {
    id: 2,
    text: "employee",
    icon: <MdQueryStats />,
    subItems: [
      { label: "add new employee", path: "add-employee", permission: "addEmployee" },
      { label: "all employees", path: "all-employee", permission: "showAllEmployees" },
    ],
  },
  {
    id: 3,
    text: "customer",
    icon: <FaWpforms />,
    subItems: [
      { label: "add new customer", path: "add-customer", permission: "addCustomer" },
      { label: "my customers", path: "emp-customers", permission: null },
      { label: "all customers", path: "all-customers", permission: "showAllCustomers" },
    ],
  },
  // 其他项同理修改
];

然后添加过滤函数,只保留有权限或无需权限的菜单项:

const filterMenuItems = (items) => {
  if (!user?.permissions) return [];
  return items.filter(item => {
    // 无需权限的直接保留
    if (item.permission === null) return true;
    // 校验权限字段是否为true
    return user.permissions[item.permission] === true;
  });
};

二、重构组件,消除重复代码

原组件中针对不同id写了大量重复的下拉逻辑,可将下拉导航项抽成独立组件,减少冗余:

// 抽离的下拉导航项组件
const DropdownNavItem = ({ text, icon, subItems, isActive, onToggle, isOpen }) => {
  const filteredSubItems = filterMenuItems(subItems);
  // 过滤后无项则不渲染该导航
  if (filteredSubItems.length === 0) return null;

  return (
    <div className="nav-item extra">
      <button
        className={`dropdown-toggle ${isActive ? "red-background" : ""}`}
        onClick={onToggle}
      >
        <div className="dropdownbtn">
          <span className="icon">{icon}</span>
          <span>{text}</span>
        </div>
        <BsChevronDown className="dropdown-icon" />
      </button>
      {isOpen && (
        <ul className="dropdown-menu">
          {filteredSubItems.map((item, index) => (
            <NavLink
              key={index}
              to={item.path}
              className={({ isActive }) =>
                isActive ? "dropdownmenu-item active" : "dropdownmenu-item"
              }
            >
              <div className="dropdown-itemcontainer">
                <span className="dropdown-itemtext">{item.label}</span>
              </div>
            </NavLink>
          ))}
        </ul>
      )}
    </div>
  );
};

主组件中用统一状态管理下拉展开和激活状态:

const NavLinks = ({ ToggleSideBar }) => {
  const { user } = useAppContext();
  // 用对象管理所有下拉的展开状态,key为导航项id
  const [dropdownStates, setDropdownStates] = useState({});
  const [activeNavId, setActiveNavId] = useState(null);

  const toggleDropdown = (id) => {
    setDropdownStates(prev => ({ ...prev, [id]: !prev[id] }));
  };

  const filterMenuItems = (items) => {
    if (!user?.permissions) return [];
    return items.filter(item => {
      if (item.permission === null) return true;
      return user.permissions[item.permission] === true;
    });
  };

  return (
    <div className="nav-links">
      {links.map((link) => {
        const { id, text, icon, subItems, path } = link;
        // 处理带下拉的导航项
        if (subItems) {
          return (
            <DropdownNavItem
              key={id}
              text={text}
              icon={icon}
              subItems={subItems}
              isActive={activeNavId === id}
              onToggle={() => {
                toggleDropdown(id);
                setActiveNavId(id);
              }}
              isOpen={dropdownStates[id]}
            />
          );
        } 
        // 处理无下拉的单链接
        else {
          return (
            <div
              key={id}
              className="nav-item extra"
              onClick={() => setActiveNavId(id)}
            >
              <ul className="dropdown-menu">
                <NavLink
                  to={path}
                  className={({ isActive }) =>
                    isActive ? "dropdownmenu-item red-background" : "dropdownmenu-item"
                  }
                >
                  <div className="dropdown-itemcontainer">
                    <span className="dropdown-itemtext">{text}</span>
                  </div>
                </NavLink>
              </ul>
            </div>
          );
        }
      })}
    </div>
  );
};

三、更优的权限控制方案

  1. 路由权限守卫:仅隐藏菜单项不够,还要拦截用户直接输入URL访问无权限页面的行为,可自定义路由组件实现:
const ProtectedRoute = ({ children, requiredPermission }) => {
  const { user } = useAppContext();
  if (!user?.permissions[requiredPermission]) {
    return <Navigate to="/" replace />;
  }
  return children;
};

// 使用示例
<Route path="/add-customer" element={
  <ProtectedRoute requiredPermission="addCustomer">
    <AddCustomerPage />
  </ProtectedRoute>
} />
  1. 统一权限配置文件:把权限标识、菜单、路由配置放到单独的permissions.js文件中,方便统一维护,避免组件内硬编码。

  2. 动态路由生成:根据用户权限动态生成路由表,适合大型项目,减少手动路由配置的工作量。

  3. 权限变更监听:如果用户权限可能在会话中更新,监听全局上下文的user变化,自动同步菜单和路由权限。


内容的提问来源于stack exchange,提问作者Mohammed M

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 08:27:12