跳至主要內容

Vue全局最大化、最小化、还原、关闭、拖拽

程序员李某某大约 3 分钟

Vue全局最大化、最小化、还原、关闭、拖拽

在基于Vue全局指令实现拖拽的基础上,丰富了最大化、最小化、还原、关闭功能。

封装全局指令

在项目的 src 目录下创建 src/utils/windowControl.js 文件,代码如下:

export default {
    install(Vue) {
        Vue.directive('window-control', {
            inserted(el, binding) {
                console.log('window-control', binding);
                // 默认配置(可通过指令参数覆盖)
                const config = {
                    draggable: false,      // 是否可拖拽
                    minimizable: false,    // 是否可最小化
                    maximizable: false,    // 是否可最大化
                    boundary: null, // 默认无限制,可传入选择器或DOM对象
                    closable: false,       // 是否显示关闭按钮
                    onClose: null,        // 关闭时的回调函数
                    onMined: null,      // 最小化时的回调函数
                    onMaxed: null,         // 最大化时的回调函数
                    ...binding.value      // 自定义配置
                };

                // 初始状态
                let isMaximized = false;
                let originalStyle = {};

                // ========== 拖拽逻辑 ==========
                if (config.draggable) {
                    el.style.position = 'absolute'; // 必须设置为绝对定位

                    el.onmousedown = function (ev) {
                        if (isMaximized) return; // 最大化时禁止拖拽

                        const disX = ev.clientX - el.offsetLeft;
                        const disY = ev.clientY - el.offsetTop;

                        document.onmousemove = function (ev) {
                            const left = ev.clientX - disX;
                            const top = ev.clientY - disY;

                            // 边界检查(防止拖出屏幕)
                            const maxLeft = window.innerWidth - el.offsetWidth;
                            const maxTop = window.innerHeight - el.offsetHeight;

                            el.style.left = Math.max(0, Math.min(left, maxLeft)) + 'px';
                            el.style.top = Math.max(0, Math.min(top, maxTop)) + 'px';
                        };

                        document.onmouseup = function () {
                            document.onmousemove = null;
                            document.onmouseup = null;
                        };
                    };
                }

                // ========== 窗口控制按钮 ==========
                if (config.minimizable || config.maximizable) {
                    const btnContainer = document.createElement('div');
                    btnContainer.className = 'window-control-btns';

                    // 最小化按钮
                    if (config.minimizable) {
                        const minBtn = document.createElement('button');
                        minBtn.innerHTML = '−';
                        minBtn.onclick = () => {
                            // 隐藏窗口
                            el.style.display = 'none';
                            // 可在这里触发自定义事件
                            if (typeof config.onMined === 'function') {
                                config.onMined(el);
                            }
                        };
                        btnContainer.appendChild(minBtn);
                    }

                    // 最大化/还原按钮
                    if (config.maximizable) {
                        const maxBtn = document.createElement('button');
                        maxBtn.innerHTML = '□';
                        maxBtn.onclick = () => {
                            if (isMaximized) {
                                // 还原窗口
                                Object.assign(el.style, originalStyle);
                                maxBtn.innerHTML = '□';
                            } else {
                                // 最大化窗口
                                if (config.boundary) {
                                    console.log(11111111, config.boundary);

                                    const boundaryEl = typeof config.boundary === 'string'
                                        ? document.querySelector(config.boundary)
                                        : config.boundary;
                                    console.log(22222222, boundaryEl);

                                    if (boundaryEl) {
                                        originalStyle = {
                                            width: el.style.width,
                                            height: el.style.height,
                                            left: el.style.left,
                                            top: el.style.top
                                        };

                                        // 限制在boundary区域内
                                        el.style.width = `${boundaryEl.offsetWidth}px`;
                                        el.style.height = `${boundaryEl.offsetHeight}px`;
                                        el.style.left = `${boundaryEl.offsetLeft}px`;
                                        el.style.top = `${boundaryEl.offsetTop}px`;
                                    }
                                } else {
                                    // 默认全屏
                                    el.style.width = '100vw';
                                    el.style.height = '100vh';
                                    el.style.left = '0';
                                    el.style.top = '0';
                                }
                                maxBtn.innerHTML = '↗';
                            }
                            isMaximized = !isMaximized;
                            if (typeof config.onMaxed === 'function') {
                                config.onMaxed(el, isMaximized);
                            }
                        };
                        btnContainer.appendChild(maxBtn);
                    }

                    // 关闭按钮(×)
                    if (config.closable) {
                        const closeBtn = document.createElement('button');
                        closeBtn.innerHTML = '×';
                        closeBtn.onclick = () => {
                            // 触发回调或自定义事件
                            if (typeof config.onClose === 'function') {
                                config.onClose(el);
                            } else {
                                // 移除窗口
                                el.remove();
                            }
                        };
                        btnContainer.appendChild(closeBtn);
                    }

                    // 将按钮容器添加到元素右上角
                    btnContainer.style.position = 'absolute';
                    btnContainer.style.right = '10px';
                    btnContainer.style.top = '10px';
                    el.appendChild(btnContainer);
                }
            }
        });
    }
};

使用插件

在 src/main.js 入口文件引入包含全局拖拽指令的插件,代码如下:

import Vue from 'vue'
import App from './App.vue'
import router from './router'
import windowControl from '@/utils/windowControl.js' // 引入插件

// 使用插件
Vue.use(windowControl)

new Vue({
  router,
  render: h => h(App)
}).$mount('#app')

在组件中使用拖拽指令

在任意的组件文件中可以通过 v-candrag 指令赋予元素可拖拽的能力,代码如下:

<template>
  <div class="drag-container">
    <div class="drag-div" v-window-control="{
          draggable: true,
          maximizable: true,
          minimizable: true,
          closable: true,
          boundary: '.main',
          onClose: handleClose,
          onMaxed: handleMaximize,
          onMined: handleMinimize,
        }"></div>
  </div>
</template>

<script>
// javascript脚本代码...
export default {
  methods: {
    handleClose(el) {
      console.log("窗口已关闭", el);
      el.remove(); // 直接移除DOM元素(或隐藏 el.style.display = 'none')
    },
    handleMinimize(el) {
      console.log("窗口已最小化", el);
    },
    handleMaximize(el, isMaximized) {
      console.log(el, `窗口状态:${isMaximized ? "最大化" : "还原"}`);
    },

  }
};
</script>

<style scoped>
.drag-container {
  width: 100%;
  height: 100%;
  position: relative; /* 把拖拽元素父元素设置为相对定位,非常重要!!! */
  background-color: bisque;
}
.drag-div {
  position: absolute; /* 把拖拽元素设置为绝对定位,非常重要!!! */
  width: 300px;
  height: 300px;
  background-color: pink;
}
</style>
上次编辑于:
贡献者: 李元昊