Vben Admin中实现Modal可拉伸功能

使用vbenadmin框架提供的Modal时,发现改组件并未提供拉伸api,而恰好遇上了该需求,改了半天终于找到可行的方法。

之前查询其他控件库modal的拉伸功能时发现大部分都是用指令实现的,所以我也采用了指令的方向,如有更好意见可在评论区提出。

指令:

// 1. 保留普通div的指令逻辑
const vResizable = {
  mounted(el) {
    initResizable(el);
  },
  unmounted(el) {
    el._cleanup?.();
  },
};

// 2. 核心初始化逻辑(严格兼容Modal样式)
function initResizable(target) {
  // 关键:不修改Modal的position(保留其默认的fixed)
  // 只添加必要的样式,避免干扰显示
  target.style.overflow = 'hidden';

  // 创建4个拖拽手柄(完全透明,不影响显示)
  const handles = {
    top: document.createElement('div'),
    right: document.createElement('div'),
    bottom: document.createElement('div'),
    left: document.createElement('div'),
  };

  Object.keys(handles).forEach((dir) => {
    const handle = handles[dir];
    handle.style.cssText = {
      top: `position:absolute; top:0; left:0; width:100%; height:8px; background:transparent; cursor:ns-resize; z-index:9999; border:none !important; pointer-events:auto;`,
      right: `position:absolute; top:0; right:0; width:8px; height:100%; background:transparent; cursor:ew-resize; z-index:9999; border:none !important; pointer-events:auto;`,
      bottom: `position:absolute; bottom:0; left:0; width:100%; height:8px; background:transparent; cursor:ns-resize; z-index:9999; border:none !important; pointer-events:auto;`,
      left: `position:absolute; top:0; left:0; width:8px; height:100%; background:transparent; cursor:ew-resize; z-index:9999; border:none !important; pointer-events:auto;`,
    }[dir]!;
    target.appendChild(handle);
  });

  let resizingDir = '';
  const start = { x: 0, y: 0, width: 0, height: 0, left: 0, top: 0 };

  // 鼠标按下事件(只记录状态,不修改样式)
  Object.keys(handles).forEach((dir) => {
    handles[dir].addEventListener('mousedown', (e) => {
      resizingDir = dir;
      start.x = e.clientX;
      start.y = e.clientY;
      start.width = target.offsetWidth;
      start.height = target.offsetHeight;
      start.left = target.offsetLeft;
      start.top = target.offsetTop;
      e.stopPropagation(); // 关键:阻止事件触发Modal关闭
      e.preventDefault();
    });
  });

  // 鼠标移动事件(只修改宽高,不修改定位)
  const handleMouseMove = (e) => {
    if (!resizingDir) return;

    // 对Modal只支持右和下拉伸(避免修改定位导致闪烁)
    if (target.classList.contains('z-popup')) {
      switch (resizingDir) {
        case 'right':
          const newW = start.width + (e.clientX - start.x);
          if (newW >= 300) target.style.width = `${newW}px`;
          break;
        case 'bottom':
          const newH = start.height + (e.clientY - start.y);
          if (newH >= 300) target.style.height = `${newH}px`;
          break;
      }
    } else {
      // 普通div支持四向拉伸
      switch (resizingDir) {
        case 'right':
          const newW = start.width + (e.clientX - start.x);
          if (newW >= 300) target.style.width = `${newW}px`;
          break;
        case 'bottom':
          const newH = start.height + (e.clientY - start.y);
          if (newH >= 300) target.style.height = `${newH}px`;
          break;
        case 'left':
          const newLeftW = start.width + (start.x - e.clientX);
          if (newLeftW >= 300) {
            target.style.width = `${newLeftW}px`;
            target.style.left = `${start.left - (start.x - e.clientX)}px`;
          }
          break;
        case 'top':
          const newTopH = start.height + (start.y - e.clientY);
          if (newTopH >= 300) {
            target.style.height = `${newTopH}px`;
            target.style.top = `${start.top - (start.y - e.clientY)}px`;
          }
          break;
      }
    }

    e.stopPropagation();
    e.preventDefault();
  };

  document.addEventListener('mousemove', handleMouseMove);

  // 鼠标松开事件
  const handleMouseUp = () => {
    resizingDir = '';
  };

  document.addEventListener('mouseup', handleMouseUp);

  // 清理逻辑
  target._cleanup = () => {
    document.removeEventListener('mousemove', handleMouseMove);
    document.removeEventListener('mouseup', handleMouseUp);
    Object.values(handles).forEach((handle) => handle.remove());
  };
}

然后在modal配置中需要手动初始化,否则加在modal上的拉伸指令不生效

// 3. Modal配置(打开后手动初始化,只支持右和下拉伸)
const [Modal, modalApi] = useVbenModal({
  //...
  async onOpenChange(isOpen: boolean) {
    if (isOpen) {
      await loadFieldInfo();
      // 延迟确保Modal完全渲染,且不干扰动画
      setTimeout(() => {
        const realModalRoot = document.querySelector('div.z-popup.bg-background[data-state="open"]');
        if (realModalRoot && !realModalRoot._resizableInited) {
          realModalRoot._resizableInited = true;
          initResizable(realModalRoot);
        }
      }, 300); // 匹配Modal的动画时长(300ms)
    } else {
      // 关闭时清理手柄,避免下次打开冲突
      const realModalRoot = document.querySelector('div.z-popup.bg-background');
      realModalRoot?._cleanup?.();
    }
  },
  title: $t('base.base-info-tip.setting'),
});

缺陷:虽然实现了拉伸功能但是非常不丝滑……还在改进中

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值