Vben Admin中给Element Dialog添加拉伸指令

import type { App, Directive } from 'vue';

// 允许在 HTMLElement 上挂载清理与初始化标记
declare global {
  interface HTMLElement {
    _cleanup?: () => void;
    _resizableInited?: boolean;
  }
}

/**
 * 核心初始化逻辑
 */
export function initResizable(target: HTMLElement) {
  const computedStyle = window.getComputedStyle(target);
  if (computedStyle.position === 'static') {
    target.style.position = 'relative';
  }
  target.style.overflow = 'hidden';

  // 拖拽手柄
  type Dir = 'bottom' | 'left' | 'right' | 'top';
  const handles: Partial<Record<Dir, HTMLDivElement>> = {};

  const cssMap: Record<'bottom' | 'left' | 'right' | 'top', string> = {
    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;',
  };

  // 仅在模态类元素上追加右/下手柄
  const isModal =
    target.classList.contains('z-popup') ||
    target.classList.contains('el-dialog');
  const dirsToAppend: readonly Dir[] = isModal
    ? (['right', 'bottom'] as const)
    : (['top', 'right', 'bottom', 'left'] as const);

  dirsToAppend.forEach((dir) => {
    const handle = document.createElement('div');
    handle.style.cssText = cssMap[dir];
    handles[dir] = handle;
    target.append(handle);
  });

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

  // 记录起始状态
  dirsToAppend.forEach((dir) => {
    const handle = handles[dir];
    if (!handle) return;
    handle.addEventListener('mousedown', (e: MouseEvent) => {
      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();
      e.preventDefault();
    });
  });

  // 鼠标移动时仅修改宽高
  const handleMouseMove = (e: MouseEvent) => {
    if (!resizingDir) return;

    if (isModal) {
      // Modal/ElDialog,只支持右与下方向,避免上/左漂移
      switch (resizingDir) {
        case 'bottom': {
          const newH = start.height + (e.clientY - start.y);
          if (newH >= 300) target.style.height = `${newH}px`;
          break;
        }
        case 'right': {
          const newW = start.width + (e.clientX - start.x);
          if (newW >= 300) target.style.width = `${newW}px`;
          break;
        }
      }
    } else {
      // 普通容器,四向拉伸
      switch (resizingDir) {
        case 'bottom': {
          const newH = start.height + (e.clientY - start.y);
          if (newH >= 50) target.style.height = `${newH}px`;
          break;
        }
        case 'left': {
          const newLeftW = start.width + (start.x - e.clientX);
          if (newLeftW >= 50) {
            target.style.width = `${newLeftW}px`;
            target.style.left = `${start.left - (start.x - e.clientX)}px`;
          }
          break;
        }
        case 'right': {
          const newW = start.width + (e.clientX - start.x);
          if (newW >= 50) target.style.width = `${newW}px`;
          break;
        }
        case 'top': {
          const newTopH = start.height + (start.y - e.clientY);
          if (newTopH >= 50) {
            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());
  };
}

/**
 * 全局指令:v-resizable
 * 已在Modal中进行全局配置,保证在Modal中生效
 */
export function registerResizableDirective(app: App) {
  const directive: Directive<HTMLElement, void> = {
    mounted(el) {
      try {
        initResizable(el);
      } catch (error) {
        console.warn('[v-resizable] init failed:', error);
      }
    },
    unmounted(el) {
      el._cleanup?.();
    },
  };

  app.directive('resizable', directive);
}

指令代码如上,以上代码已将指令全局配置给Vben中的Modal模态框。详细可见我上一篇帖子Vben Admin中实现Modal可拉伸功能

在registerResizableDirective中末尾加入以下代码

// 注册时即开启对ElDialog的全局绑定
  const bindDialogs = () => {
    document
      .querySelectorAll('.el-overlay .el-dialog, .el-dialog')
      .forEach((d) => {
        const h = d as HTMLElement;
        if (!h.dataset.resizableBound) {
          h.dataset.resizableBound = '1';
          initResizable(h);
        }
      });
  };
  bindDialogs();
  new MutationObserver(() => bindDialogs()).observe(document.body, {
    childList: true,
    subtree: true,
  });

即可为ElDialog全局配置可拉伸。

以上代码非最终代码,存在多处可改进的地方,后期经过几次修改修复了其中一些问题。比如,拉伸指令用在modal中时会有拉伸动画异常的问题,需要在加监听器时加上target.style.transition = 'none';

并且使用offsetWidth来计算偏移量在上下拖拽时出现“瞬移”的情况,所以后期使用computedStyle进行了替换。

除此之外代码还存在手柄先创建后使用等问题,这里不一一赘述,有需要的可以自行修改

### 实现 Vben Admin 中全局引入 Element-Plus 组件库 要在 Vben Admin 项目中实现 Element-Plus 组件库的全局导入,可以按照以下方法操作: #### 修改 `vite.config.ts` 文件 在 Vben Admin 使用的是 Vite 构建工具,因此需要调整其配置文件来支持 Element-Plus 的全局加载。打开项目的根目录下的 `vite.config.ts` 文件,在其中添加或修改插件部分以启用自动导入功能。 以下是具体的代码示例: ```typescript import { defineConfig } from 'vite'; import AutoImport from 'unplugin-auto-import/vite'; // 自动导入插件 import Components from 'unplugin-vue-components/vite'; // Vue 组件按需加载插件 import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; export default defineConfig({ plugins: [ AutoImport({ resolvers: [ElementPlusResolver()], // 启用 Element-Plus 的自动导入解析器 }), Components({ resolvers: [ElementPlusResolver()], // 启用 Element-Plus 的组件解析器 }), ], }); ``` 上述设置会通过插件机制完成对 Element-Plus 所有组件及其样式的全局注册[^1]。 #### 安装必要的依赖包 为了确保能够正常使用 Element-Plus 提供的功能模块以及图标资源,还需要执行以下命令安装额外的支持库: ```bash pnpm add element-plus pnpm install @element-plus/icons-vue ``` 这些命令分别用于下载核心组件库和图标集合[^2]。 #### 调整样式处理逻辑 如果遇到样式冲突或者缺失的情况,则可能是因为 CSS 文件未被正确注入到应用当中。可以在入口 JavaScript 或 TypeScript 文件里手动引入整个主题风格定义: ```javascript // main.ts or main.js import 'element-plus/dist/index.css'; // 引入默认的主题样式表 ``` 此步骤非常重要,它能有效解决因缺少基础布局而导致界面显示异常的问题[^3]。 另外需要注意的是,当采用 CDN 方式部署静态资源时,记得同步更新 HTML 模板内的 `<link>` 标签指向远程服务器地址。 --- ####
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值