当前位置: 首页 > article >正文

前端vue左侧树的一整套功能实现(一):vue2+vite封装v-resize指令,实现左侧树拖拽宽度和折叠展开

实现v-resize指令,具体以下功能:

  1. 指令接收宽度最大最小值,接收一个id用于localStorage存储拖拽宽度,接收padding
  2. 拖拽时产生虚线拖拽,松开鼠标再进行元素宽度调整
  3. 折叠展开图标使用本地图片

封装一个vite下使用本地图片的函数方法

用于拖拽指令中设置折叠展开图标

/** vite使用动态图片的方式 */
export function requireImg(name) {
  return new URL(`/src/assets/imgs/${name}`, import.meta.url).href
}

v-resize指令具体代码

// 注意,需要去除绑定元素的overflow:auto,指令会添加一个具有overflow:auto的元素
let resize;
let stopResize;
const vResize = {
  bind(el, binding) {
    // 从绑定值中获取最小宽度和最大宽度
    const {
      minWidth = 150,
      maxWidth = 400,
      id = '',
      padding = 10,
    } = binding.value || {};

    // 拖拽元素内部插入一个包裹元素,方便控制所有子元素隐藏显示
    const wrapper = document.createElement('div');
    wrapper.style.cssText = `width: 100%; height: 100%; overflow: auto; padding:${padding}px`;
    while (el.firstChild) {
      // appendChild 方法用于将一个节点添加到另一个节点的子节点列表的末尾。
      // 如果要添加的节点已经存在于文档树中,appendChild 方法会将该节点从其当前位置移动到新的位置,而不是复制该节点。
      // 这样就可以将 el 的所有子元素通过循环全部插入至 wrapper 中
      wrapper.appendChild(el.firstChild);
    }
    el.appendChild(wrapper);

    // 创建拖拽元素
    const resizer = document.createElement('div');
    resizer.style.cssText =
      'width: 10px; height: 100%; position: absolute; right: -10px; top: 0px; cursor: ew-resize; user-select: none; display: flex; justify-content: center; align-items: center; z-index: 999;';
    el.style.position = 'relative';
    el.style.padding = '0px';
    el.appendChild(resizer);
    el.style.transition = 'width 0.3s ease';

    // 缓存宽度
    const savedWidth = localStorage.getItem('WIDTH' + id);
    if (savedWidth) {
      el.style.width = savedWidth;
      if (el.style.width === '0px') {
        wrapper.style.display = 'none';
      }
    }

    // 创建切换按钮
    const img = document.createElement('img');
    img.src =
      el.style.width === '0px'
        ? requireImg('tree/7.png')
        : requireImg('tree/6.png');
    img.style.cssText = 'cursor:pointer;height:40px;';
    resizer.appendChild(img);

    // 切换显示/隐藏逻辑
    img.addEventListener('mousedown', (e) => {
      e.stopPropagation();
      toggleContainer(el, wrapper, img);
    });

    // 拖拽虚线
    const line = document.createElement('div');
    line.style.cssText =
      'position: absolute; height: 100%; width: 2px; right: 0; top: 0; z-index: 9999; border-right: 0px dashed #409EFF; pointer-events: none;';
    el.appendChild(line);

    // 拖拽事件
    resizer.addEventListener('mousedown', () => {
      document.addEventListener('mousemove', resize);
      document.addEventListener('mouseup', stopResize);
    });

    let newWidth;

    resize = function (e) {
      line.style.borderRight = '2px dashed #409EFF';
      // 使用传入的最小宽度和最大宽度
      const width = e.pageX - el.getBoundingClientRect().left;
      if (width <= 0) {
        newWidth = 0;
      } else {
        newWidth = Math.max(minWidth, Math.min(maxWidth, width));
      }
      line.style.right = `${el.getBoundingClientRect().right - e.pageX}px`;
    };

    stopResize = function () {
      line.style.borderRight = '0px dashed #409EFF';
      document.removeEventListener('mousemove', resize);
      document.removeEventListener('mouseup', stopResize);
      if (newWidth) {
        el.style.width = `${newWidth}px`;
        setTimeout(() => {
          wrapper.style.display = 'block';
        }, 200);
      } else {
        el.style.width = `0px`;
        wrapper.style.display = 'none';
      }
      img.src =
        newWidth === 0 ? requireImg('tree/7.png') : requireImg('tree/6.png');
      localStorage.setItem('WIDTH' + id, el.style.width);
    };

    function toggleContainer(el, wrapper, img) {
      if (el.style.width === '0px') {
        el.style.width = `${minWidth}px`;
        img.src = requireImg('tree/6.png');
        setTimeout(() => {
          wrapper.style.display = 'block';
        }, 200);
      } else {
        wrapper.style.display = 'none';
        el.style.width = '0px';
        img.src = requireImg('tree/7.png');
      }
      localStorage.setItem('WIDTH' + id, el.style.width);
    }
  },
  unbind() {
    // 清除所有事件监听器,防止内存泄漏
    document.removeEventListener('mousemove', resize);
    document.removeEventListener('mouseup', stopResize);
  },
};

Vue.directive('resize', vResize);

使用指令

 <div class="page">
    <div
      class="left"
      v-resize="{
        id: 'left_tree',
        minWidth: '473',
        maxWidth: '773',
      }"
      v-loading="treeloading">
      ...
    </div>
    <div class="right">
      ...
    </div>
  </div>
.page{
   display:flex;
   .left_tree{
     width:550px;
   }
   .right{
     flex:1;
   }
}

http://www.kler.cn/a/316913.html

相关文章:

  • Java 网络编程(一)—— UDP数据报套接字编程
  • MTSET可溶于DMSO、DMF、THF等有机溶剂,并在水中有轻微的溶解性,91774-25-3
  • 设计模式-七个基本原则之一-迪米特法则 + 案例
  • OpenGL【C++】台灯
  • Coggle数据科学 | RAG编码模型对比:谁与OpenAI最为相似?
  • 使用electron-egg把vue项目在linux Ubuntu环境下打包并安装运行
  • Java面向对象——内部类(成员内部类、静态内部类、局部内部类、匿名内部类,完整详解附有代码+案例)
  • 江协科技STM32学习- P14 示例程序(定时器定时中断和定时器外部时钟)
  • web基础—dvwa靶场(十一)CSP Bypass
  • Linux相关概念和重要知识点(6)(make、makefile、gdb)
  • SQLServer数据分页
  • Python 中的函数装饰器:理解 @property、Getter 和 Setter 方法
  • (算法)大数的进制转换
  • ESP32-WROOM-32 [创建AP站点-客户端-TCP透传]
  • PostgreSQL中的regexp_split_to_table函数详解,拆分字段为多行
  • C++之STL—vector容器进阶篇
  • C++ STL全面解析:六大核心组件之一----序列式容器(vector和List)(STL进阶学习)
  • H5网页嵌在APP内部 手机锁屏后再打开 setInterval会重复执行
  • 【Git原理与使用】版本管理与分支管理(1)
  • LIN总线CAPL函数—— 设置报头同步间隔场长度(linSetBreakLength)
  • Redis数据结构之list列表
  • 116页可编辑PPT全面了解数据治理体系、平台,数据质量数据标准
  • Algo-Lab 2 Stack Queue ADT
  • 重修设计模式-设计原则
  • 图像生成大模型imagen
  • 【STM32 Blue Pill编程】-SPI主机和从机通信(两个STM32之间SPI通信)