react路由根据用户角色设置权限校验

2023-11-08

react路由根据用户角色设置权限校验

前言

做前端项目的时候,我们经常会有这样的情况:一个系统会有多种权限不同的角色,每个角色都有自己能访问的模块角色间能访问的模块(页面)并不完全相同,因此我们经常会有根据不同的角色管理不同的给=每个路由不同的访问权限的需求。

思路

  • 方法1: 先通过http请求获取用户信息,拿到用户权限角色,然后根据不同的角色去动态的生成路由(Routes),该方法实际是可行的,但是有个缺点,比如我们有路由,路径为/a/b,该路由我们规定SYS_ADMIN能访问而其他角色不能访问,那么我们由于是根据权限生成路由然后传参给useRoute这个hook的,因此会导致 其他用户访问的时候会显示是404找不到该页面。
  • 方法2: 在每个路由组件中先判断用户的权限在渲染对应的内容,该方法同样可行,问题是这样很麻烦,需要在每个路由组建中去写权限控制逻辑。
  • 方法3:类似于vue-routerrouter.beforeEach钩子,我们在路由表中每个路由都定义自己可以被访问的权限,然后在每次路由放生变化的时候先判断该用户是否有权限访问,如果有权限就返回对应的路由组件,否则就给到对应的提示信息(也可以是返回一个用于提示没有权限的组件)。这样做的优点就是我们只需要写一套逻辑,一劳永逸,后边添加新的模块的时候只需要在路由表里给到对应的权限即可。

实现

我的习惯就是先枚举出所有的路由路径(因为通常情况下该路径会被多个地方使用,比如左侧菜单兰,header的面包屑等等)。

// route.config.ts
export enum Pathnames {
  Login = "/login",
  SignUpPage = "/signup",
  ResetPassword = "/login/resetPassword",
  Dashboard = "",
  UserPage = "/user",
  Profile = "/user/profile",
  AdminManagement = "/user/admin-management",
  OrgnizationManagement = "/user/orgnization",
  RegistTemplate = "/reg-template",
  CreateRegTemplate = "/reg-template/create",
  EditRegTemplate = "/reg-template/:id",
  Geofence = "/geofence",
  CreateGeofence = "/geofence/create",
  GeofenceDetail = "/geofence/:id",
  Pipelines = "/pipelines",
  EditPipeline = "/pipelines/edit/:id",
  ViewPipelineDetail = "/pipelines/view/:id",
  ViewPipelineRunDetail = "/pipelines/view/:pipelineId/runs/:runId",
  Devices = "/device-management",
  GroupDetail = "/groups/:id",
  DeviceDetail = "/devices/:id",
  SettingsTemplate = "/settings-template",
  CreateSettingsTemplate = "/settings-template/create",
  EditSettingsTemplate = "/settings-template/:id",
  OtaUpgrade = "/ota",
  Applications = "/application",
  FileManagement = "/file",
  MainLayout = "/",
  TenantManagement = "/tenants",
  LicenseManagement = "/licenses",
  NotFound = "*",
}

然后定义出系统所有的角色

// user.model.ts
export enum Authority {
  TENANT_ADMIN = "TENANT_ADMIN",
  GENERAL_ADMIN = "GENERAL_ADMIN",
  OBSERVE_ADMIN = "OBSERVE_ADMIN",
  SYS_ADMIN = "SYS_ADMIN",
  GROUP_ADMIN = "GROUP_ADMIN",
  GROUP_OBSERVE_ADMIN = "GROUP_OBSERVE_ADMIN",
}

export const GENARAL_AUTHORITY = [Authority.TENANT_ADMIN, Authority.GENERAL_ADMIN, Authority.GROUP_ADMIN, Authority.OBSERVE_ADMIN, Authority.GROUP_OBSERVE_ADMIN];

export const TENANT_AND_GENERAL = [Authority.TENANT_ADMIN, Authority.GENERAL_ADMIN];

export const NOT_GROUP_ADMIN = [Authority.TENANT_ADMIN, Authority.GENERAL_ADMIN, Authority.OBSERVE_ADMIN];

export const NOT_OBSERVE_ADMIN = [Authority.TENANT_ADMIN, Authority.GENERAL_ADMIN, Authority.GROUP_ADMIN];

export const ALL_AUTHORITY = [Authority.TENANT_ADMIN, Authority.GENERAL_ADMIN, Authority.OBSERVE_ADMIN, Authority.SYS_ADMIN, Authority.GROUP_ADMIN, Authority.GROUP_OBSERVE_ADMIN];

接着就是生成所有的路由表(因为我使用的react-router-dom的版本时6.x,因此我选择使用useRoutes钩子来生成路由组件)

// routes/MainRoute.tsx
import { RouteObject, useNavigate } from "react-router-dom";
import { ALL_AUTHORITY, Authority, GENARAL_AUTHORITY, NOT_GROUP_ADMIN, NOT_OBSERVE_ADMIN, TENANT_AND_GENERAL } from "../models/user.model";
import { useEffect } from "react";
import LazyImport from "../components/common/tools/LazyImport";
import { Pathnames } from "./route.config";
const TenantManagement = LazyImport(() => import("../pages/TenantManagement"));
const LicenseManagement = LazyImport(() => import("../pages/LicenseManagement"));
const NotFound = LazyImport(() => import("../components/common/tools/NotFound"));
const Dashboard = LazyImport(() => import("../components/dashboard/Dashboard"));
const MainLayout = LazyImport(() => import("../components/layouts/MainLayout"));
const Login = LazyImport(() => import("../components/system/Login"));
const SignUpPage = LazyImport(() => import("../components/system/SignUpPage"));
const RegistTemplate = LazyImport(() => import("../pages/RegistTemplate"));
const CreateRegTemplate = LazyImport(() => import("../components/regist-template/CreateRegTemplate"));
const Devices = LazyImport(() => import("../pages/Devices"));
const SettingsTemplate = LazyImport(() => import("../pages/SettingsTemplate"));
const CreateSettingsTemplate = LazyImport(() => import("../components/settings-template/CreateSettingsTemplate"));
const Applications = LazyImport(() => import("../pages/Applications"));
const FileManagement = LazyImport(() => import("../pages/FileManagement"));
const GroupDetail = LazyImport(() => import("../components/devices/config/GroupDetail"));
const DeviceDetail = LazyImport(() => import("../components/devices/config/DeviceDetail"));
const UserPage = LazyImport(() => import("../pages/UserPage"));
const Profile = LazyImport(() => import("../components/admin/Profile"));
const AdminManagement = LazyImport(() => import("../components/admin/AdminManagement"));
const OrgnizationManagement = LazyImport(() => import("../components/admin/OrgnizationManagement"));
const ResetPassword = LazyImport(() => import("../components/common/tools/ResetPassword"));
const Geofence = LazyImport(() => import("../pages/Geofence"));
const CreateGeofence = LazyImport(() => import("../components/geofence/CreateGeofence"));
const ViewGeofenceDetail = LazyImport(() => import("../components/geofence/ViewGeofenceDetail"));
const Pipelines = LazyImport(() => import("../pages/Pipelines"));
const EditPipeline = LazyImport(() => import("../components/pipelines/EditPipeline"));
const ViewPipelineDetail = LazyImport(() => import("../components/pipelines/ViewPipelineDetail"));
const ViewPipelineRunDetail = LazyImport(() => import("../components/pipelines/ViewPipelineRunDetail"));

// 自定义react-router v5之前的Redirect组件
export function Redirect({ to }) {
  let navigate = useNavigate();
  useEffect(() => {
    navigate(to);
  });
  return null;
}

export interface RouteWithArgs extends RouteObject {
  path: Pathnames;
  auth?: Authority[];
  children?: RouteWithArgs[];
}

export const allRoutes: RouteWithArgs[] = [
  {
    element: Login,
    path: Pathnames.Login,
  },
  {
    element: SignUpPage,
    path: Pathnames.SignUpPage,
  },
  {
    element: ResetPassword,
    path: Pathnames.ResetPassword,
  },
  {
    element: MainLayout,
    path: Pathnames.MainLayout,
    auth: GENARAL_AUTHORITY,
    children: [
      {
        element: Dashboard,
        path: Pathnames.Dashboard,
        auth: ALL_AUTHORITY,
      },
      {
        element: UserPage,
        path: Pathnames.UserPage,
        auth: [Authority.TENANT_ADMIN],
        children: [
          {
            element: <Redirect to={"/user/profile"}></Redirect>,
            path: Pathnames.UserPage,
          },
          {
            element: Profile,
            path: Pathnames.Profile,
          },
          {
            element: AdminManagement,
            path: Pathnames.AdminManagement,
          },
          {
            element: OrgnizationManagement,
            path: Pathnames.OrgnizationManagement,
          },
        ],
      },
      {
        element: RegistTemplate,
        path: Pathnames.RegistTemplate,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: CreateRegTemplate,
        path: Pathnames.CreateRegTemplate,
        auth: TENANT_AND_GENERAL,
      },
      {
        element: CreateRegTemplate,
        path: Pathnames.EditRegTemplate,
        auth: TENANT_AND_GENERAL,
      },
      {
        element: Geofence,
        path: Pathnames.Geofence,
        auth: NOT_GROUP_ADMIN,
      },
      {
        element: CreateGeofence,
        path: Pathnames.CreateGeofence,
        auth: TENANT_AND_GENERAL,
      },
      {
        element: ViewGeofenceDetail,
        path: Pathnames.GeofenceDetail,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: Pipelines,
        path: Pathnames.Pipelines,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: EditPipeline,
        path: Pathnames.EditPipeline,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: ViewPipelineDetail,
        path: Pathnames.ViewPipelineDetail,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: ViewPipelineRunDetail,
        path: Pathnames.ViewPipelineRunDetail,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: Devices,
        path: Pathnames.Devices,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: GroupDetail,
        path: Pathnames.GroupDetail,
        auth: NOT_OBSERVE_ADMIN,
      },
      {
        element: DeviceDetail,
        path: Pathnames.DeviceDetail,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: SettingsTemplate,
        path: Pathnames.SettingsTemplate,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: CreateSettingsTemplate,
        path: Pathnames.CreateSettingsTemplate,
        auth: TENANT_AND_GENERAL,
      },
      {
        element: CreateSettingsTemplate,
        path: Pathnames.EditSettingsTemplate,
        auth: TENANT_AND_GENERAL,
      },
      {
        element: Applications,
        path: Pathnames.Applications,
        auth: GENARAL_AUTHORITY,
      },
      {
        element: FileManagement,
        path: Pathnames.FileManagement,
        auth: GENARAL_AUTHORITY,
      },
    ],
  },
  {
    element: MainLayout,
    path: Pathnames.MainLayout,
    auth: [Authority.SYS_ADMIN],
    children: [
      {
        element: <Redirect to="/tenants" />,
        auth: [Authority.SYS_ADMIN],
        path: Pathnames.Dashboard,
      },
      {
        element: TenantManagement,
        path: Pathnames.TenantManagement,
        auth: [Authority.SYS_ADMIN],
      },
      {
        element: LicenseManagement,
        path: Pathnames.LicenseManagement,
        auth: [Authority.SYS_ADMIN],
      },
      {
        element: UserPage,
        path: Pathnames.UserPage,
        auth: [Authority.SYS_ADMIN],
        children: [
          {
            element: <Redirect to={"/user/profile"}></Redirect>,
            path: Pathnames.UserPage,
          },
          {
            element: Profile,
            path: Pathnames.Profile,
          },
        ],
      },
    ],
  },
  {
    element: NotFound,
    path: Pathnames.NotFound,
  },
];

export function createRoutes(auth: Authority): RouteWithArgs[] {
  return allRoutes.filter((route) => !route.auth || route.auth.includes(auth));
}

我给每个路由都添加了权限信息(auth字段,没有该字段代表不需要权限就能访问),数据类型为Authority的数组集合。

然后在编写路由组件

// routes/Index.tsx
import { useDispatch, useSelector } from "react-redux";
import { useCallback, useEffect, useMemo } from "react";
import { useLocation, useRoutes } from "react-router-dom";
import { createRoutes } from "./MainRoute";
import { setLicenseInfoAction, setUserFetchStatus, setWidth } from "../store/actions/config.action";
import { selectAuthority } from "../store/selectors";
import { Store } from "../models/store.model";
import CommonLoading from "../components/common/tools/CommonLoading";
import { userController } from "../controllers/user.controller";
import { licenseController } from "../controllers/license.controller";
import Login from "../components/system/Login";
import * as debounce from "debounce";
import { decryptExpireTime } from "../utils";

const Routes = () => {
  const auth = useSelector(selectAuthority);

  const routes = useMemo(() => {
    return createRoutes(auth);
  }, [auth]);

  const locationData = useLocation();

  const dispatch = useDispatch();

  const getLicenseExpireTime = useCallback(() => {
    const result = licenseController.getLicenseExpireTime();
    result
      .then((res) => {
        localStorage.setItem("licenseExpire", res);
        let expireTimeStamp = decryptExpireTime(res);
        if (expireTimeStamp === -1) {
          const existLicense = false;
          dispatch(setLicenseInfoAction({ existLicense }));
        } else {
          const existLicense = true;
          const licenseExpire = expireTimeStamp > Date.now() ? false : true;
          dispatch(setLicenseInfoAction({ existLicense, licenseExpire }));
        }
      })
      .catch(() => {});
    return result;
  }, [dispatch]);

  // 一进来首先校验token
  const checkUserInfo = useCallback(() => {
    userController
      .validateJwtToken()
      .then(() => {
        getLicenseExpireTime().finally(() => {
          dispatch(setUserFetchStatus(true));
        });
      })
      .catch(() => {
        dispatch(setUserFetchStatus(true));
      });
  }, [dispatch, getLicenseExpireTime]);

  // 设置屏幕宽度监听器
  const watchWindowWidthDebounce = useMemo(() => {
    return function () {
      dispatch(setWidth(window.innerWidth, window.innerHeight));
      window.onresize = debounce(() => {
        dispatch(setWidth(window.innerWidth, window.innerHeight));
      }, 300);
    };
  }, [dispatch]);

  useEffect(() => {
    watchWindowWidthDebounce();
    checkUserInfo();
  }, [checkUserInfo, watchWindowWidthDebounce]);

  const getUserInfoFinish = useSelector((state: Store) => state.config.getUserInfoFinish);

  const RouteComponent = useRoutes(routes);

  // 通过useRoutes传入配置的路由表来生成路由组建,因此暴露出去的Routes其实就是一个路由组件
  if (locationData.pathname === "/login") {
    return <Login />;
  } else if (getUserInfoFinish) {
    return RouteComponent;
  } else {
    return <CommonLoading title={null}></CommonLoading>;
  }
};

export default Routes;

由于我们所有的需要权限控制的页面都在MainLayout.tsx组件中,因此我们在这里去做权限控制(实际上直接在路由组件Index.tsx中也是可以的)

import { Box } from "@mui/material";
import { memo, useMemo } from "react";
import { useSelector } from "react-redux";
import { matchRoutes, Navigate, Outlet, RouteMatch, useLocation } from "react-router-dom";
import { Store } from "../../models/store.model";
import { isMobile, selectAuthority, selectUserLoginStatus } from "../../store/selectors";
import CommonLoading from "../common/tools/CommonLoading";
import UploadLicense from "../layouts/UploadLicense";
import Content from "./Content";
import HeaderBar from "./HeaderBar";
import NavMenu from "./NavMenu";
import NavMenuDrawer from "./NavMenuDrawer";
import "leaflet.markercluster";
import "leaflet.markercluster/dist/MarkerCluster.css";
import "leaflet.markercluster/dist/MarkerCluster.Default.css";
import { allRoutes, RouteWithArgs } from "../../routes/MainRoute";
import { Pathnames } from "../../routes/route.config";
import NoPermissionPage from "../common/tools/NoPermissionPage";

const MainLayout = memo(() => {
  const state = useSelector((state: Store) => state.config);
  const _isMobile = useSelector(isMobile);
  const { isLogin, licenseValid } = useSelector(selectUserLoginStatus);
  const auth = useSelector(selectAuthority);

  const { getUserInfoFinish, collapsed } = state;

  const { pathname } = useLocation();

  const hasPermission = useMemo(() => {
    // 判断用户是否有权限访问该页面
    const matchedRoutes: RouteMatch<Pathnames>[] = matchRoutes(allRoutes, pathname);
    if (!matchedRoutes?.length) {
      return;
    }
    // 这里通过matchedRoutes中的auth和当前用户的auth来对比判断是否有权限访问
    const matchedRoute = matchedRoutes[matchedRoutes.length - 1];
    // @ts-ignore
    const route: RouteWithArgs = matchedRoute?.route;
    const allowedAuth = route?.auth;
    return !allowedAuth || allowedAuth.includes(auth);
  }, [pathname, auth]);

  const contentWidth = useMemo(() => {
    if (_isMobile) {
      return "100%";
    }
    return collapsed && !_isMobile ? "calc(100% - 240px)" : "calc(100% - 80px)";
  }, [collapsed, _isMobile]);

  if (!getUserInfoFinish) {
    // 如果还在获取用户信息阶段  就展示loading界面
    return <CommonLoading></CommonLoading>;
  } else if (isLogin && licenseValid) {
    // 如果用户权限校验通过
    return (
      <Box
        sx={{
          width: "100%",
          height: "100%",
          display: "flex",
          justifyContent: "space-between",
        }}
      >
        {_isMobile ? <NavMenuDrawer collapsed={collapsed}></NavMenuDrawer> : <NavMenu></NavMenu>}
        <Box sx={{ height: 1 / 1, width: contentWidth, transition: "width .3s" }}>
          <HeaderBar></HeaderBar>
          {/* 判断用户是否有权限访问该页面,如果没有权限就渲染没有权限的页面 */}
          <Content>{hasPermission ? <Outlet /> : <NoPermissionPage />}</Content>
        </Box>
      </Box>
    );
  } else if (!licenseValid) {
    // 如果用户license过期
    return <UploadLicense></UploadLicense>;
  } else {
    // 如果用户权限校验不通过 就跳转到登录页面
    return <Navigate to="/login" />;
  }
});

export default MainLayout;

我们在hasPermission方法中实现了判断有没有权限的逻辑

<Content>{hasPermission ? <Outlet /> : <NoPermissionPage />}</Content>),

然后再根据hasPermission的值返回对应的的结果,即:

如果hasPermission为true,代表有权限,返回路由组件<Outlet />,反之返回没有权限的提示组件

在该组件中我们即可提示用户没有权限:

import { Box, Typography, Button } from "@mui/material";
import { useTranslation } from "react-i18next";
import MatDialog from "./MatDialog";
import { useNavigate } from "react-router-dom";

export default function NoPermissionPage() {
  const navigate = useNavigate();
  const { t } = useTranslation();

  const navigateToHome = () => navigate("/");
  return (
    <Box>
      <MatDialog open={true} hideFooter onClose={null} size="sm" title={null}>
        <Box className="flex flex-column">
          <Typography variant="h3">{t("user.noPermissionForThisPage")}</Typography>
          <Button sx={{ mt: 4, mb: 2 }} variant="contained" onClick={navigateToHome}>
            {t("common.backHome")}
          </Button>
        </Box>
      </MatDialog>
    </Box>
  );
}

效果:

比如对于同一页面,有权限的角色访问:
在这里插入图片描述

无权限的角色访问:

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

react路由根据用户角色设置权限校验 的相关文章

随机推荐

  • open函数的使用

    简述 open是UNIX系统 包括LINUX Mac等 的系统调用函数 区别于C语言库函数fopen include
  • t6服务器虚拟打印机,priPrinter Professional(免费的虚拟打印机) V6.6.0 中文免费版

    priPrinter Professional是一款免费的虚拟打印机 它可以处理非常强大的打印作业 可以帮助用户重新排列页面信息 当用户打印完成的时候 还可以直接预览pdf文件 priPrinter 可以帮助您与重新排列页面或作业 删除边距
  • 香港科技大学(广州)物联网学域李松泽教授课题组现招收博士后研究员、全奖博士、硕士研究生(2023秋季入学)

    香港科技大学 广州 物联网学域李松泽教授课题组现招收博士后研究员 全奖博士 硕士研究生 2023秋季入学 同时开放科研助理 科研访问学生等职位申请 李老师个人简介 Songze Li https songzli github io 李松泽博
  • 硬件知识-ADC模数转换芯片

    ADC的分辨率与精度 精度 是用来描述物理量的准确程度的 而 分辨率 是用来描述刻度划分的 分辨率与AD芯片的位数有关 而精度需要查看手册看参数 对于ADC 确定输入大小 Vin OutputcodeLSB 如果ADC的输出代码为二进制或二
  • 微信小程序——抽奖之九宫格

  • discuz-ucenter-api-for-java的学习

    需求 Java的主网站 注册时同步到discuz的数据库 实现 参考http code google com p discuz ucenter api for java 1 在discuz的UCenter的应用管理中 添加一个应用 应用类型
  • 【编译原理】概述

    第一章 概述 1 1 编译器概述 1 1 1 基本概念 翻译器 能够完成从一种语言到另一种语言的保语义变换的软件称为翻译器 这两种语言分别称为该翻译器的源语言和目标语言 编译器 是一种翻译器 它的特点是目标语言比源语言低级 编译 将高级语言
  • Vue 3的Diff算法相比Vue 2有哪些具体的改进?

    Vue 3的Diff算法相比Vue 2进行了一些具体的改进 主要包括以下几个方面 Patch flag Vue 3引入了Patch flag的概念 用于标记组件在更新过程中的一些特殊情况 例如组件的props发生变化或只需要强制更新等 这样
  • Ubuntu下Linux系统文件恢复

    Linux 常见数据恢复工具 Linux 系统中有许多工具能帮助我们完成误删数据的恢复工作 较常见的工具有 foremsot extundelete scalpel 这三种命令行工具以及 testdisk 和 phtorec 这两种字符终端
  • Github桌面版托管本地静态web网页

    Github桌面版托管本地静态web网页 1 注册GitHub账号 记录注册账户名称 2 下载GitHub客户端 GitHub desktop 下载 3 网页端创建仓库 仓库名为 账户名称 github io 设置GitHub page 查
  • Linux修改文件出现错误E45:“readonly” option is set(add ! to override)退出不了vim

    出现这种错误时会退出不了vim 那么出现这种错误的原因有 1 该错误为当前用户没有权限对文件修改 2 该文件没有正确保存退出 正在打开状态 关闭后再保存 3 若该文件所有都关闭 提示有的人没有关闭 则删除该文件的临时文件则可正常打开 修改
  • Spring 事务传播行为

    如果对事务不了解的可以先看下 我的上一篇文章 数据库事务详解 概述 一般SSH的项目都是使用三层架构即Controller Services DAO Spring 的事务一般都在Services定义 而Controller DAO都不定义事
  • LVGL入门 常用的几个命令(个人笔记)

    前言 学习LVGL的过程中 常常知道有这个命令 也知道大概怎么用 但总想不起来命令叫什么 在整个库中找也显得麻烦 搞得每次写程序还要翻之前的Demo 所以在这里将学习过程中用到的命令 存放在这里 方便再使用的时候方便的找到命令名字 lv o
  • 深度学习速成(11)BiLSTM

    BiLSTM即双向长短期记忆网络 Bidirectional Long Short Term Memory BiLSTM 最早由J rgen Schmidhuber和Sepp Hochreiter等人于1997年在论文 Long short
  • 激光雷达与相机外参标定(附open3d python代码)

    现在的激光雷达与相机的标定程序基本都是Ubuntu框架下面的 并且都是C 代码 需要安装的依赖也比较复杂 于是自己写了一个python版本的标定程序 依赖非常简单 Windows系统也可以运行 并且代码简单一个文件搞定 符合python简单
  • 2.2 面向对象(分类和static)

    1 案例驱动模式 1 1案例驱动模式概述 理解 通过我们已掌握的知识点 先实现一个案例 然后找出这个案例中 存在的一些问题 在通过新知识点解决问题 1 2案例驱动模式的好处 理解 解决重复代码过多的冗余 提高代码的复用性 解决业务逻辑聚集紧
  • uniapp小程序,根据小程序的环境版本,控制的显页面功能按钮的示隐藏

    需求 根据小程序环境控制控制页面某个功能按钮的显示隐藏 下面是官方文档和功能实现的相关代码 实现上面需要 用到了uni getAccountInfoSync uni getAccountInfoSync 是一个 Uniapp 提供的同步方法
  • qnap 文件传输服务器,如何将 QNAP NAS 作为 RADIUS 服务器使用?

    QNAP NAS 的远程身份验证拨入用户服务 RADIUS 服务器功能可以为要连接并使用网络服务的计算机提供集中身份验证和授权管理 系统用户帐户仅支持 PAP EAP TLS PAP 和 EAP TTLS PAP 身份验证 仅支持 WPA
  • 使用Electron打包Http地址为应用程序

    使用Electron打包Http地址为应用程序 NodeJS环境安装 下载NodeJS 安装NodeJS 配置镜像地址 配置npm镜像地址 配置Electron镜像地址 编辑项目配置文件 编辑package js文件 编辑main js文件
  • react路由根据用户角色设置权限校验

    react路由根据用户角色设置权限校验 前言 做前端项目的时候 我们经常会有这样的情况 一个系统会有多种权限不同的角色 每个角色都有自己能访问的模块角色间能访问的模块 页面 并不完全相同 因此我们经常会有根据不同的角色管理不同的给 每个路由