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;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值