React 实现通过鼠标拖拽动态改变左侧菜单栏宽度

import { ProLayout } from "@ant-design/pro-components";
import { Layout } from "antd";
import React, { FC, useEffect, useState, useRef } from "react";
import useUserStore from "/@/store/userStore";
import { shallow } from "zustand/shallow";
import { Outlet } from "react-router-dom";
import Breadcrumb from "/@/components/Breadcrumb";
import HeaderComponent from "/@/components/Header";
import CustomBoundary from "/@/components/Error";
import Menu from "/@/components/Menu";
import { Collapse, logo } from "/@/utils/png";
import styles from "./index.module.less";

const { Header, Sider } = Layout;

const headerStyle: React.CSSProperties = {
  position: "fixed",
  top: 0,
  right: 0,
  textAlign: "center",
  height: 48,
  zIndex: 10,
  background: "var(--m-primary-linear-gradient-color)",
  boxShadow: "0px 2px 8px 0px rgba(0,0,0,0.15)",
};

const siderStyle: React.CSSProperties = {
  position: "fixed",
  top: 0,
  left: 0,
  zIndex: 11,
  height: "100%",
  borderRight: "1px solid #E5E5E5",
  cursor: "col-resize",
};

const siderBlockStyle: React.CSSProperties = {
  overflow: "hidden",
  transition: "all 0.18s,background 0s",
};

const collapsedTrue = {
  width: "80px",
  flex: "0 0 80px",
  maxWidth: "80px",
  minWidth: "80px",
};

const Index: FC = () => {
  const { selectMenuKey } = useUserStore(
    (state) => ({
      selectMenuKey: state.selectMenuKey,
    }),
    shallow
  );
  const siderRef = useRef(null);
  const [collapsed, setCollapsed] = useState(false);
  const [siderBlockTrends, setSiderBlockTrends] = useState({
    width: "290px",
    flex: "0 0 290px",
    maxWidth: "290px",
    minWidth: "290px",
  });
  const [siderWidth, setSiderWidth] = useState(290);
  const [isDragging, setIsDragging] = useState(false);
  const [collapsedFalse, setCollapsedFalse] = useState({
    width: "290px",
    flex: "0 0 290px",
    maxWidth: "290px",
    minWidth: "290px",
  });

  const onToggleCollapsed = (): void => {
    setCollapsed(!collapsed);
  };

  /**
   * 监听左侧菜单栏是否收起
   * collapsed true: 收起  false: 展开
   */
  useEffect(() => {
    console.log("collapsed", collapsed);
    setSiderBlockTrends(!collapsed ? collapsedFalse : collapsedTrue);
    setSiderWidth(collapsed ? 80 : 290);
    setCollapsedFalse(
      collapsed
        ? {
            width: `80px`,
            flex: `0 0 80px`,
            maxWidth: `80px`,
            minWidth: `80px`,
          }
        : {
            width: `290px`,
            flex: `0 0 290px`,
            maxWidth: `290px`,
            minWidth: `290px`,
          }
    );
  }, [collapsed]);

  // 监听左侧菜单栏宽度,大于收起宽度即展开,小于等于即收起
  useEffect(() => {
    setCollapsed(siderWidth <= 80 ? true : false);
  }, [siderWidth]);

  // 根据菜单栏宽度变化会更新菜单栏底部占位元素的样式, 同时监听到菜单栏底部占位元素的样式变化时,实时更新给底部占位元素
  useEffect(() => {
    setSiderBlockTrends(collapsedFalse);
  }, [collapsedFalse]);

  // 这里是监听页面的mousemove,mousedown,mouseup事件,记得要在useEffect的return中销毁
  useEffect(() => {
    const handleMouseMove = (e: MouseEvent) => {
      const { clientX } = e;
      if (isDragging) {
        const maxClientX = window.innerWidth / 3;
        const newWidth =
          Math.min(clientX, maxClientX) >= 80
            ? Math.min(clientX, maxClientX)
            : 80;
        setSiderWidth(newWidth);
        setCollapsedFalse({
          width: `${newWidth}px`,
          flex: `0 0 ${newWidth}px`,
          maxWidth: `${newWidth}px`,
          minWidth: `${newWidth}px`,
        });
      }
    };


    const handleMouseDown = (e: MouseEvent) => {
      // 判断当前点击的元素,是否在左侧菜单的元素上,并且通过isDragging变量来控制是否允许更新菜单栏宽度
      if (
        siderRef.current &&
        (siderRef.current as any).contains(e.target) &&
        e.clientX > (siderRef.current as any).getBoundingClientRect().right - 10
      ) {
        document.body.style.userSelect = "none";
        setIsDragging(true);
      }
    };

    const handleMouseUp = () => {
      document.body.style.userSelect = "auto";
      setIsDragging(false);
    };

    document.addEventListener("mousemove", handleMouseMove);
    document.addEventListener("mousedown", handleMouseDown);
    document.addEventListener("mouseup", handleMouseUp);

    return () => {
      document.removeEventListener("mousemove", handleMouseMove);
      document.removeEventListener("mousedown", handleMouseDown);
      document.removeEventListener("mouseup", handleMouseUp);
    };
  }, [isDragging]);

  return (
    <Layout hasSider>
      <div style={{ ...siderBlockStyle, ...siderBlockTrends }}></div>
      <Sider
        ref={siderRef}
        width={siderWidth}
        style={siderStyle}
        collapsed={collapsed}
        theme="light"
        breakpoint="lg"
        onBreakpoint={(broken) => {
          setSiderBlockTrends(broken ? collapsedTrue : collapsedFalse);
          setCollapsed(broken);
        }}
        trigger={null}
      >
        <div className={styles.siderInner}>
          <>
            <div className={styles.logoContainer}>
              <div
                style={{
                  width: `${collapsed ? "27px" : "100%"}`,
                  overflow: "hidden",
                  transition: "width 0.3s linear 0.2s",
                }}
              >
                <img src={logo} />
              </div>
            </div>
            <Menu collapsed={collapsed} />
          </>

          {
            <div
              className={styles.diyCollapsed}
              style={{
                backgroundImage: `url(${Collapse})`,
                transform: `translateX(50%) rotateY(${collapsed ? 180 : 0}deg)`,
                transition: "transform 0.3s linear 0.2s",
              }}
              onClick={onToggleCollapsed}
            ></div>
          }
        </div>
      </Sider>
      <Layout>
        <Header style={headerStyle}>
          <HeaderComponent />
        </Header>
        // 如果使用了ProLayout组件,视图缩放比例变小后,会自动显示logo等组件自带样式
        // 需要增加 headerRender={false} 属性来取消配置
        <ProLayout
          menuRender={false}
          style={{ paddingTop: 48 }}
          ErrorBoundary={CustomBoundary}
          headerRender={false}
        >
          {!selectMenuKey.some((item) => item?.includes("dashboard")) && (
            <Breadcrumb />
          )}
          <Outlet />
        </ProLayout>
      </Layout>
    </Layout>
  );
};

export default Index;
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以使用React Router和React Context来实现动态权限菜单。首先,根据用户的角色或权限,动态生成菜单项。然后,使用React Router来管理路由,根据菜单项的URL来渲染对应的组件。最后,使用React Context来传递用户的角色或权限信息,以便在组件中进行权限控制。 ### 回答2: React 是一个用于构建用户界面的 JavaScript 库,可以用于实现动态权限菜单。 动态权限菜单意味着菜单的内容和显示方式会根据用户的权限动态加载和展示。在 React 中,可以通过以下步骤来实现动态权限菜单: 1. 定义权限列表:首先,需要确定菜单项的权限列表,即每个菜单项所对应的权限。可以将权限列表定义为一个数组或对象,其中每个权限都有一个唯一的标识符。 2. 获取用户权限:在 React 组件中,可以通过调用后端 API 或使用其他方式来获取当前用户的权限列表。可以将用户权限列表存储在组件的状态中。 3. 过滤菜单项:根据用户的权限列表,过滤出当前用户具备权限的菜单项。可以使用数组的 `filter` 方法或其他方式来实现。 4. 渲染菜单项:使用 React 的组件和 JSX 语法,根据过滤后的菜单项数组,动态生成菜单。可以使用循环、条件渲染等技术来确保只渲染用户具备权限的菜单项。 5. 添加交互功能:为每个菜单项添加必要的交互功能,例如点击菜单项后触发相应的路由跳转或显示子菜单等。 通过以上步骤,就可以在 React实现动态权限菜单。这样,无论用户的权限如何变化,菜单都会根据其权限动态加载和展示。 ### 回答3: React是一个用于构建用户界面的JavaScript库,它提供了许多强大的功能来创建交互式和动态的Web页面。使用React实现动态权限菜单可以通过以下步骤完成: 1. 配置路由:首先,需要使用React Router或类似的路由组件来设置应用程序的路由。这可以让我们根据用户的权限加载不同的页面或组件。 2. 获取用户权限:接下来,需要从后端或认证服务获取用户的权限信息。这可以是一个API请求,返回用户的权限数组或权限标记。 3. 创建菜单组件:根据用户的权限列表,创建一个动态菜单组件。这个组件会渲染用户有权限访问的菜单项,并且可以使用递归来处理多级菜单。 4. 权限验证:在菜单组件中,根据用户的权限列表对每个菜单项进行权限验证。如果用户拥有访问该菜单项的权限,将其显示在菜单中,否则隐藏或禁用。 5. 跳转处理:当用户点击菜单项时,根据菜单项的路由配置,使用React Router或自定义的路由处理函数来处理页面跳转。 6. 动态加载组件:根据菜单项的路由配置,可以使用React的按需加载功能,动态加载相应的组件。这样可以提高应用程序的性能,同时避免加载不相关的组件。 总结起来,使用React实现动态权限菜单可以分为配置路由、获取用户权限、创建菜单组件、权限验证、跳转处理和动态加载组件等步骤。这样可以根据不同用户的权限动态显示可访问的菜单项,提供更好的用户体验和数据安全性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值