From 2dfbf14c9e723f6e7edeed5521cd33ad3901f7b9 Mon Sep 17 00:00:00 2001
From: LCJ-MinYa <1049468118@qq.com>
Date: Wed, 22 Oct 2025 10:29:41 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8A=A8=E6=80=81=E8=B0=83=E6=95=B4ele?=
=?UTF-8?q?ment-plus=E5=BC=B9=E7=AA=97dialog=E7=9A=84=E5=A4=A7=E5=B0=8F?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/router/modules/demo.ts | 4 +
.../resizeElementDialog/directives/resize.js | 74 ++++++
src/views/demo/resizeElementDialog/index.vue | 61 +++++
.../resizeElementDialog.md | 223 ++++++++++++++++++
4 files changed, 362 insertions(+)
create mode 100644 src/views/demo/resizeElementDialog/directives/resize.js
create mode 100644 src/views/demo/resizeElementDialog/index.vue
create mode 100644 src/views/demo/resizeElementDialog/resizeElementDialog.md
diff --git a/src/router/modules/demo.ts b/src/router/modules/demo.ts
index cb16983..1e946bb 100644
--- a/src/router/modules/demo.ts
+++ b/src/router/modules/demo.ts
@@ -138,6 +138,10 @@ const titleArr = [
key: 'base64Tool',
title: 'base64编码解码工具',
},
+ {
+ key: 'resizeElementDialog',
+ title: '动态调整element弹窗大小',
+ },
];
// @/views/demo/**/*.vue
diff --git a/src/views/demo/resizeElementDialog/directives/resize.js b/src/views/demo/resizeElementDialog/directives/resize.js
new file mode 100644
index 0000000..6064ccb
--- /dev/null
+++ b/src/views/demo/resizeElementDialog/directives/resize.js
@@ -0,0 +1,74 @@
+export default {
+ updated(el) {
+ let minWidth = 600;
+ let minHeight = 200;
+ const dialogHeaderEl = el.querySelector('.el-dialog__header');
+ const dragDom = el.querySelector('.el-dialog');
+ if (!dialogHeaderEl || !dragDom) return;
+
+ dragDom.style.overflow = 'auto'; // 滚动条导致无法缩放(通过加大右下角添加的dom即可修复)
+ // dragDom.style.overflow = 'hidden';
+ dialogHeaderEl.onselectstart = () => false;
+ dialogHeaderEl.style.cursor = 'move';
+
+ const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+ let moveDown = (e) => {
+ const disX = e.clientX - dialogHeaderEl.offsetLeft;
+ const disY = e.clientY - dialogHeaderEl.offsetTop;
+
+ let styL = +sty.left.replace(/px/g, '');
+ let styT = +sty.top.replace(/px/g, '');
+
+ document.onmousemove = function (e) {
+ const l = e.clientX - disX;
+ const t = e.clientY - disY;
+ dragDom.style.left = `${l + styL}px`;
+ dragDom.style.top = `${t + styT}px`;
+ };
+ document.onmouseup = () => {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ };
+ };
+ dialogHeaderEl.onmousedown = moveDown;
+
+ // 创建一个拉伸块的函数(右下角)
+ function createResizeHandle(cursor, styles, onMouseMoveHandler) {
+ const handle = document.createElement('div');
+ Object.assign(handle.style, {
+ position: 'absolute',
+ zIndex: '99',
+ ...styles,
+ cursor,
+ });
+ dragDom.appendChild(handle);
+ handle.onmousedown = (e) => {
+ document.onmousemove = (ev) => {
+ ev.preventDefault();
+ onMouseMoveHandler(ev, e);
+ };
+ document.onmouseup = () => {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ };
+ };
+ }
+
+ // 示例:右下角拖拽
+ createResizeHandle(
+ 'se-resize',
+ {
+ width: '20px',
+ height: '20px',
+ right: '0px',
+ bottom: '0px',
+ },
+ (ev, e) => {
+ let width = ev.clientX - dragDom.getBoundingClientRect().left;
+ let height = ev.clientY - dragDom.getBoundingClientRect().top;
+ dragDom.style.width = width > minWidth ? width + 'px' : minWidth + 'px';
+ dragDom.style.height = height > minHeight ? height + 'px' : minHeight + 'px';
+ }
+ );
+ },
+};
diff --git a/src/views/demo/resizeElementDialog/index.vue b/src/views/demo/resizeElementDialog/index.vue
new file mode 100644
index 0000000..3ff7c23
--- /dev/null
+++ b/src/views/demo/resizeElementDialog/index.vue
@@ -0,0 +1,61 @@
+
+
+
+
+ 点击弹出dialog
+
+
+
+
+
+
+
+ 这是一个dialog弹窗
+ 可以通过拖拽改变弹窗大小
+ 可以通过点击右上角关闭弹窗
+ 这是一个dialog弹窗
+ 可以通过拖拽改变弹窗大小
+ 可以通过点击右上角关闭弹窗
+ 这是一个dialog弹窗
+ 可以通过拖拽改变弹窗大小
+ 可以通过点击右上角关闭弹窗
+
+
+
+
+
+
+
+
+
+
diff --git a/src/views/demo/resizeElementDialog/resizeElementDialog.md b/src/views/demo/resizeElementDialog/resizeElementDialog.md
new file mode 100644
index 0000000..d4bb263
--- /dev/null
+++ b/src/views/demo/resizeElementDialog/resizeElementDialog.md
@@ -0,0 +1,223 @@
+## 代码详解
+
+当然可以,下面是对这段 Vue.js 自定义指令(`updated` 钩子)代码的**逐行详细解释**,帮助你全面理解其功能、原理和实现细节。
+
+---
+
+## 🎯 **整体功能**
+这段代码是一个 Vue 的自定义指令(或更准确地说,是一个 **组件的 `updated` 生命周期钩子**),用于:
+> **让 `el-dialog` 组件支持“拖拽移动”和“右下角缩放”**。
+
+它通过原生 DOM 操作,在 `el-dialog` 被更新后,动态添加拖拽和缩放功能,使其像桌面窗口一样可拖动、可调整大小。
+
+---
+
+## ✅ 一、核心结构:`updated(el)`
+```js
+updated(el) {
+```
+- `updated` 是 Vue 组件的生命周期钩子,当组件的 VDOM 更新后执行。
+- `el` 是该指令作用的 DOM 元素(即 `` 的根节点)。
+- 这里利用 `updated` 在 DOM 渲染完成后注入行为,确保元素存在。
+
+---
+
+## ✅ 二、定义最小尺寸
+```js
+let minWidth = 600;
+let minHeight = 200;
+```
+- 设置对话框的**最小宽度和高度**,防止用户把窗口拖得太小。
+- 保证用户体验:不会出现“看不见内容”的情况。
+
+---
+
+## ✅ 三、获取关键 DOM 元素
+```js
+const dialogHeaderEl = el.querySelector('.el-dialog__header');
+const dragDom = el.querySelector('.el-dialog');
+```
+- `dialogHeaderEl`:对话框的标题栏区域,用于拖拽移动。
+- `dragDom`:整个对话框容器(`.el-dialog`),用于移动和缩放操作。
+- 如果没有找到 `dragDom`,直接 `return`,避免报错。
+
+---
+
+## ✅ 四、设置样式与禁用选择
+```js
+dragDom.style.overflow = 'hidden';
+dialogHeaderEl.onselectstart = () => false;
+dialogHeaderEl.style.cursor = 'move';
+```
+- `overflow: hidden`:防止拖拽过程中出现滚动条或内容溢出。
+- `onselectstart = () => false`:禁止在标题栏上选中文本(避免误选)。
+- `cursor: move`:鼠标悬停在标题栏时显示“移动”光标,提升交互提示。
+
+---
+
+## ✅ 五、获取当前样式(兼容 IE 和现代浏览器)
+```js
+const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null);
+```
+- `currentStyle`:IE 浏览器使用。
+- `getComputedStyle`:现代浏览器使用。
+- 获取当前 `dragDom` 的 `left`、`top` 等样式值,用于后续计算偏移量。
+
+---
+
+## ✅ 六、拖拽移动功能(核心逻辑)
+```js
+let moveDown = (e) => {
+ const disX = e.clientX - dialogHeaderEl.offsetLeft;
+ const disY = e.clientY - dialogHeaderEl.offsetTop;
+```
+- `moveDown` 是鼠标按下时的回调函数。
+- `disX`, `disY`:记录鼠标点击位置与标题栏左上角的偏移量(即“相对偏移”)。
+- 这是实现“拖拽”的关键:鼠标在移动时,用这个偏移量计算新位置。
+
+```js
+let styL = +sty.left.replace(/px/g, '');
+let styT = +sty.top.replace(/px/g, '');
+```
+- 把 `left` 和 `top` 的字符串值(如 `"200px"`)转成数字(如 `200`)。
+- 用于后续计算:新位置 = 鼠标位置 - 偏移量 + 原始位置。
+
+```js
+document.onmousemove = function (e) {
+ const l = e.clientX - disX;
+ const t = e.clientY - disY;
+ dragDom.style.left = `${l + styL}px`;
+ dragDom.style.top = `${t + styT}px`;
+};
+```
+- `onmousemove`:鼠标移动时触发。
+- 计算新位置:`新 left = 鼠标 X - 偏移 X + 原始 left`
+- 动态设置 `left` 和 `top`,实现拖拽移动。
+
+```js
+document.onmouseup = () => {
+ document.onmousemove = null;
+ document.onmouseup = null;
+};
+```
+- `onmouseup`:鼠标松开时,清除事件监听,防止内存泄漏。
+
+```js
+dialogHeaderEl.onmousedown = moveDown;
+```
+- 将 `moveDown` 绑定到标题栏的 `mousedown` 事件,启动拖拽。
+
+---
+
+## ✅ 七、创建缩放 handle(右下角拖拽)
+```js
+function createResizeHandle(cursor, styles, onMouseMoveHandler) {
+ const handle = document.createElement('div');
+ Object.assign(handle.style, {
+ position: 'absolute',
+ zIndex: '99',
+ ...styles,
+ cursor,
+ });
+ dragDom.appendChild(handle);
+ handle.onmousedown = (e) => {
+ document.onmousemove = (ev) => {
+ ev.preventDefault();
+ onMouseMoveHandler(ev, e);
+ };
+ document.onmouseup = () => {
+ document.onmousemove = null;
+ document.onmouseup = null;
+ };
+ };
+}
+```
+- **封装一个创建“缩放句柄”的工具函数**。
+- `cursor`:光标样式(如 `se-resize` 表示右下角可缩放)。
+- `styles`:CSS 样式,比如宽高、位置。
+- `onMouseMoveHandler`:鼠标移动时的回调函数,用于处理缩放逻辑。
+- 创建一个 `div` 作为缩放手柄,添加到 `dragDom` 中。
+- 手柄在 `mousedown` 时绑定 `mousemove` 和 `mouseup`,实现缩放。
+
+---
+
+## ✅ 八、添加右下角缩放手柄
+```js
+createResizeHandle(
+ 'se-resize',
+ {
+ width: '10px',
+ height: '10px',
+ right: '0px',
+ bottom: '0px',
+ },
+ (ev, e) => {
+ let width = ev.clientX - dragDom.getBoundingClientRect().left;
+ let height = ev.clientY - dragDom.getBoundingClientRect().top;
+ dragDom.style.width = width > minWidth ? width + 'px' : minWidth + 'px';
+ dragDom.style.height = height > minHeight ? height + 'px' : minHeight + 'px';
+ }
+);
+```
+- 在对话框**右下角**添加一个 `10px × 10px` 的缩放手柄。
+- `se-resize`:表示“右下角可缩放”。
+- `right: 0px`, `bottom: 0px`:定位在右下角。
+- `onMouseMoveHandler` 中:
+ - `getBoundingClientRect().left/top`:获取对话框当前左上角坐标。
+ - `ev.clientX - left`:计算当前宽度。
+ - `ev.clientY - top`:计算当前高度。
+ - 限制最小宽高:`width > minWidth`,避免窗口过小。
+
+---
+
+## ✅ 九、总结:功能一览
+| 功能 | 实现方式 |
+|------|----------|
+| ✅ 拖拽移动 | 标题栏 `mousedown`,通过 `clientX/Y` 和偏移量计算新位置 |
+| ✅ 禁止选中文本 | `onselectstart = () => false` |
+| ✅ 右下角缩放 | 创建 `div` 手柄,监听 `mousemove` 动态修改 `width` 和 `height` |
+| ✅ 最小尺寸限制 | 通过 `minWidth` / `minHeight` 保护窗口不被缩放过小 |
+| ✅ 防止样式溢出 | `overflow: hidden` |
+
+---
+
+## ✅ 十、使用场景建议
+- 适合用于 **需要自定义窗口行为的弹窗**,比如:
+ - 自定义的“设置面板”
+ - “可视化编辑器”中的浮动窗口
+ - “调试工具”弹窗
+- 但注意:
+ - 不建议在移动端使用(拖拽体验差)。
+ - 不能用于 `v-model` 控制的普通弹窗,需配合 `v-if` 或 `v-show` 确保 DOM 存在。
+
+---
+
+## ✅ 十一、可扩展性建议
+你可以在 `createResizeHandle` 中添加更多方向的手柄:
+```js
+// 例如:添加右上角、左下角、左上角缩放
+createResizeHandle('ne-resize', { width: '10px', height: '10px', right: '0', top: '0' }, ...);
+createResizeHandle('sw-resize', { width: '10px', height: '10px', left: '0', bottom: '0' }, ...);
+createResizeHandle('nw-resize', { width: '10px', height: '10px', left: '0', top: '0' }, ...);
+```
+
+---
+
+## ✅ 十二、注意事项
+1. **需确保 `el-dialog` 的 `class` 为 `.el-dialog`**,否则 `querySelector` 会失败。
+2. **该代码在 `updated` 钩子中执行,仅当组件更新时生效**,若对话框是动态渲染,需确保 `updated` 被触发。
+3. **建议封装为一个全局指令**,以便复用:
+ ```js
+ Vue.directive('drag-dialog', { updated });
+ ```
+ 然后在模板中使用:
+ ```vue
+
+ ```
+
+---
+
+## ✅ 总结一句话:
+> 这段代码通过原生 DOM 操作,在 `el-dialog` 上动态添加“可拖拽移动”和“右下角缩放”功能,实现类似桌面窗口的交互体验,适用于需要高度自定义弹窗行为的场景。
+
+如有需要,我可以帮你将其封装成一个可复用的 Vue 指令或组件。
\ No newline at end of file