vue二次封装ant-design-vue中的Modal弹窗组件,实现拖拽,全屏两种功能,原有参数属性不变

news2024/12/28 6:44:56

在我们的项目的有的地方需要用弹框的拖拽,以及弹窗自定义全屏显示的需求,所以再次将二次合一,同时弹框里面内容自适应屏幕高度

在ant-design-vue中,已经实现了拖拽,全屏的功能,下面是ant官网的示例

自定义渲染对话框

全屏

下面是对ant原有的功能进行二次封装,由于组件拆解了,子组件分为三部分:为如下代码:

一:子级的根目录modal下新建index.vue,弹框涉及到的参数可参考ant官网

<template>
<!--  v-bind处理a-table 传递过来的参数,你们开发的时候不用再子组件中接收这么ant原有的参数-->
    <a-modal class="Kld-dfs-modal" :open="isOpen" :body-style="computeBodyStyle" :width="width" :centered="centered"
        @cancel="closeModal" :afterClose="handleAfterClose" :destroyOnClose="destroyOnClose" :keyboard="keyboardOpen"
        :style="topStyle" :maskClosable="maskClosable" :mask="mask" :wrap-class-name="fullModal" v-bind="attrs">
        <template #title>

            <div class="modalHeader">
<!-- 标题的ref用于控制是否可以拖拽-->
                <div :ref="modalTitleRefS" class="draggableTitle">{{ title }}</div>
<!-- 弹框头部的标题,以及全屏的按钮-->
                <ModalHeader :width="width" @fullScreen="handleFullScreen" @reduction="handleReduction" />
            </div>
        </template>
        <template #closeIcon>
<!-- 弹框头部的关闭按钮区域-->
            <ModalClose />
        </template>
        <div ref="bodySlot">
<!-- 弹框内容区域-->
            <slot name="body"></slot>
        </div>
        <template #footer>
<!-- 弹框底部按钮区域-->
            <div ref="footerSlot">
                <slot name="footer" v-if="haveFooter"></slot>
            </div>
        </template>
<!-- 实现是否拖拽弹框计算的核心代码-->
        <template #modalRender="{ originVNode }">
            <div :style="transformStyle">
                <component :is="originVNode" />
            </div>
        </template>
    </a-modal>
</template>
  
<script  lang="ts">
import { defineComponent, ref, computed, watch, CSSProperties, watchEffect } from 'vue';
import { useDraggable } from '@vueuse/core';
import { FullscreenExitOutlined, FullscreenOutlined, CloseOutlined } from '@ant-design/icons-vue';

import ModalClose from './components/ModalClose.vue';
import ModalHeader from './components/ModalHeader.vue';
import { Modal } from 'ant-design-vue';

export default defineComponent({
    name: 'KldDfsModal',
    props: {
        centered: {
            type: Boolean,
            default: true
        },
        maskClosable: {
            type: Boolean,
            default: true,
        },
        mask: {
            type: Boolean,
            default: true
        },
        topStyle: {
            type: String,
            default: ''
        },
        closable: {
            type: Boolean,
            default: true,
        },
        destroyOnClose: {
            type: Boolean,
            default: false,
        },
        keyboardOpen: {
            type: Boolean,
            default: false,
        },
        title: {
            type: String,
            default: ''
        },
        open: {
            type: Boolean,
            default: false
        },
        haveFooter: {
            type: Boolean,
            default: true
        },
        width: {
            type: [String, Number],
            default: '60vw'
        },
        boxHeight: {
            type: [String, Number],
            default: 60
        },
        bodyStyle: {
            type: Object,
            default: {
                overflowX: 'hidden',
                overflowY: 'auto'
            }
        }
    },
    setup(props, { attrs, emit }) {
        /*****拖拽相关*****/
        const modalTitleRefS = ref('modalTitleRef')
        const modalTitleRef = ref<HTMLElement>();
        const { x, y, isDragging } = useDraggable(modalTitleRef);
        const startX = ref<number>(0);
        const startY = ref<number>(0);
        const startedDrag = ref(false);
        const transformX = ref(0);
        const transformY = ref(0);
        const preTransformX = ref(0);
        const preTransformY = ref(0);
        const dragRect = ref({ left: 0, right: 0, top: 0, bottom: 0 });
        const transformStyle = computed<CSSProperties>(() => {
            return {
                transform: `translate(${transformX.value}px, ${transformY.value}px)`,
            };
        });
        watch([x, y], () => {
            if (!startedDrag.value) {
                startX.value = x.value;
                startY.value = y.value;
                const bodyRect = document.body.getBoundingClientRect();
                const titleRect = modalTitleRef.value?.getBoundingClientRect() ?? { width: 0, height: 0 };
                dragRect.value.right = bodyRect.width - titleRect.width;
                dragRect.value.bottom = bodyRect.height - titleRect.height;
                preTransformX.value = transformX.value;
                preTransformY.value = transformY.value;
            }
            startedDrag.value = true;
        });
        watch(isDragging, () => {
            if (!isDragging) {
                startedDrag.value = false;
            }
        });
        watchEffect(() => {
            if (startedDrag.value) {
                transformX.value =
                    preTransformX.value +
                    Math.min(Math.max(dragRect.value.left, x.value), dragRect.value.right) -
                    startX.value;
                transformY.value =
                    preTransformY.value +
                    Math.min(Math.max(dragRect.value.top, y.value), dragRect.value.bottom) -
                    startY.value;
            }
        });
        /*****************/
        const bodySlot = ref(null);
        const footerSlot = ref(null);

        const isOpen = ref<boolean>(false);
        //const centered = ref<boolean>(true);
        const computeBodyStyle = ref<CSSProperties>();
        //动态计算内容高度,生成弹窗
        const computeWindowStyle = (bodyRealHeight: number = 0, headerRealHeight: number = 0, footerRealHeight: number = 0) => {
            let windowHeight = document.body.offsetHeight;
            let realHeight: number = 0;
            //后面增加数值的构成 model padding 上下20 + 20 header和body之间 25 footer和body之间 20
            if (bodyRealHeight + headerRealHeight + footerRealHeight + 85 >= windowHeight) {
                realHeight = windowHeight - headerRealHeight - footerRealHeight - 82;
            } else {
                realHeight = bodyRealHeight + 25;
            }
            computeBodyStyle.value = Object.assign({
                height: `${realHeight}px`
            }, props.bodyStyle);


        };
        const width = ref<string | number>(props.width);
        const fullModal = ref<string>();
        // 全屏
        const handleFullScreen = () => {
            width.value = '100%'
            modalTitleRefS.value = ''
            fullModal.value = 'kld-full-modal'
            transformX.value = 0;
            transformY.value = 0;
        }
        // 还原
        const handleReduction = () => {
            width.value = props.width
            modalTitleRefS.value = 'modalTitleRef'
            fullModal.value = ''
        }
        const closeModal = (e: Event) => {
            emit('cancel', e);
        };
        /**
         * @description: Modal 完全关闭后的回调
         */
        const handleAfterClose = () => {
            console.log('Modal 完全关闭后的回调');
            fullModal.value = ''
            modalTitleRefS.value = 'modalTitleRef'
            width.value = props.width
            emit('afterClose')
        };
        watch(
            () => props.open,
            (newVal) => {
                if (newVal) {
                    isOpen.value = true;
                } else {
                    isOpen.value = false;
                }
            },
            { deep: true }
        );
        watch(() => bodySlot.value, (newVal) => {
            if (newVal) {
                const bodyDom: any = newVal, footerDom: any = footerSlot.value, headerDom: any = modalTitleRef.value;
                x.value = startX.value;
                y.value = startY.value;
                computeWindowStyle(bodyDom.clientHeight, headerDom.clientHeight, footerDom.clientHeight);
            }
        }, { deep: true });
        return {
            modalTitleRefS,
            isOpen,
            modalTitleRef,
            computeBodyStyle,
            width,
            fullModal,
            transformStyle,
            handleFullScreen,
            handleReduction,
            closeModal,
            handleAfterClose,
            bodySlot,
            footerSlot,
            transformX,
            transformY,
            dragRect,
            startedDrag,
            isDragging,
            computeWindowStyle,
            startX,
            startY,
            preTransformX,
            preTransformY,
            x,
            y,
            attrs,
            listeners: emit,
        };
    },
    components: {
        CloseOutlined,
        FullscreenOutlined,
        FullscreenExitOutlined,
        AModal: Modal,
        ModalClose,
        ModalHeader
    }

});





</script>
  
<style scoped lang="less">
.draggableTitle {
    width: 100%;
    cursor: move
}
<!-- 用于弹框滚动条-->
:global(.ant-modal-root .ant-modal-wrap) {
    overflow: hidden;
}

:deep(.ant-modal-body)::-webkit-scrollbar {
    width: 0px;
    height: 0px;
    padding: 0px;
}

:deep(.ant-modal-body)::-webkit-scrollbar-thumb {
    background: #c1c1c1;
    border-radius: 3px;
}

:deep(.ant-modal-body)::-webkit-scrollbar-track {
    border-radius: 3px;
    background: #f1f1f1;
}
</style>
<style lang="less">
<!-- 用于弹框全屏的样式-->
@import url('./common.less');
</style>
  

子级的根目录modal下新建common.less,全屏的cs样式

// modal 全屏
.modalHeader {
  display: flex;
  justify-content: space-between;
}
.kld-full-modal{
  .ant-modal {
    max-width: 100% !important;
    top: 0 !important;
    padding-bottom: 0 !important;
    margin: 0 !important;
  }
  .ant-modal-content {
    display: flex;
    flex-direction: column;
    height: calc(100vh);
  }
  .ant-modal-body {
    flex: 1;
    max-height: calc(100vh);
  }
}

二:子级的根目录modal下新建components文件然后新建存放头部关闭按钮的组件以及全屏按钮的组件,

ModalClose组件

<template>
    <a-tooltip ref="KldTooltip" color="#ffffff" placement="bottom">
        <template #title>
            <span style="color: black;">关闭</span>
        </template>
        <CloseOutlined />
    </a-tooltip>
</template>
  
<script lang="ts">
import { ref } from 'vue';
import { Tooltip } from 'ant-design-vue';
import { CloseOutlined } from '@ant-design/icons-vue';

export default {
    name: "KldTooltip",
    setup(_, { attrs, emit }) {
        return {
            attrs,
            listeners: emit,
            KldTooltip: ref()
        };
    },
    components: {
        ATooltip: Tooltip,
        CloseOutlined
    }
};
</script>
  
  

ModalHeader组件

<template>
    <div>
        <a-tooltip color="#ffffff" v-if="width != '100%'" placement="bottom">
            <template #title>
                <span style="color: black;">全屏</span>
            </template>
            <a-button type="text" class="ant-modal-close" style="margin-right: 30px;" @click="handleFullScreen">
                <FullscreenOutlined />
            </a-button>
        </a-tooltip>
        <a-tooltip color="#ffffff" v-else placement="bottom">
            <template #title>
                <span style="color: black;">还原</span>
            </template>
            <a-button type="text" class="ant-modal-close" style="margin-right: 30px;" @click="handleReduction">
                <FullscreenExitOutlined />
            </a-button>
        </a-tooltip>
    </div>
</template>
  
<script lang="ts">
import { ref } from 'vue';
import { Tooltip } from 'ant-design-vue';
import { FullscreenExitOutlined, FullscreenOutlined } from '@ant-design/icons-vue';

export default {
    name: "KldTooltip",
    props: {
        width: {
            type: [String, Number],
            default: '40%'
        },
    },
    setup(props, { attrs, emit }) {
        const handleFullScreen = () => {
            emit('fullScreen');
        };
        const handleReduction = () => {
            emit('reduction');
        };
        return {
            props,
            attrs,
            listeners: emit,
            KldTooltip: ref(),
            handleFullScreen,
            handleReduction
        };
    },
    components: {
        ATooltip: Tooltip,
        FullscreenExitOutlined,
        FullscreenOutlined
    }
};
</script>
  
  

 三、然后在main.js里面引入,也可以直接在父组件引入,此处就不讲解引入了,直接父组件使用弹框

父组件

<template>
      <kld-dfs-modal :open="createVisible" :title="createTitle" :width="'40%'" :destroyOnClose="true" :haveFooter="true"
            :boxHeight="85" @cancel="createVisible = false" :maskClosable="false">
            <template #body>
                <ApplicationCreate @close="handleClose" :editFormRef="editFormRef" :editId="editId"
                    :createTitle="createTitle" />
                <p class="h-20" v-for="index in 20" :key="index">根据屏幕高度自适应</p>
            </template>
            <template #footer>
                <a-button>
                    测试底部按钮插槽
                </a-button>
            </template>
        </kld-dfs-modal>
</template>
  
<script lang="ts" setup>
const createVisible = ref<boolean>(false); //创建
const createTitle = ref<string>(""); //创建弹窗标题

const showModalA = () => {
    createVisible.value = true;
    createTitle.value = "创建应用";
    editFormRef.value = {}
    editId.value = ''
};
// 关闭创建弹窗
const handleClose = (type: string) => {
    if (type === "提交") {

    }
    createVisible.value = false;
};
</script>
  
  

效果图如下:内容可自适应屏幕高度,如果不需要可通过弹框标题的ref控制

如果在使用中有什么问题,还请多多指点,也可以私信或者留言,觉得可用麻烦点点赞以及收藏

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1394058.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE进阶】 关于应用分层

文章目录 &#x1f38b;序言&#x1f343;什么是应⽤分层&#x1f38d;为什么需要应⽤分层&#x1f340;如何分层(三层架构)&#x1f384;MVC和三层架构的区别和联系&#x1f333;什么是高内聚低耦合⭕总结 &#x1f38b;序言 在我们进行项目开发时我们如果一股脑将所有代码都…

零售EDI:Babylist EDI 项目案例

Babylist 与各种不同的品牌和零售商合作&#xff0c;包括婴儿用品、玩具、衣物和其他相关产品的制造商。用户可以在 Babylist 上浏览各种不同的产品&#xff0c;并根据自己的需求和喜好选择适合的项目。本文将为大家介绍对接Babylist 的EDI项目案例。 Babylist EDI 需求 传输协…

升级8.0:民生手机银行的“内容解法”

数字化浪潮&#xff0c;滚滚来袭。 随着数字中国建设的持续推进&#xff0c;数字经济正在蓬勃发展。中商产业研究院分析师预测&#xff0c;2023年中国数字经济市场规模将增长至56.7万亿元&#xff0c;占GDP的比重将达到43.5%。 在此浪潮下&#xff0c;数字化的触角蔓延到各行…

[C++] opencv - copyTo函数介绍和使用案例

copyTo函数介绍 copyTo函数是OpenCV库中的一个成员函数&#xff0c;用于将一个Mat对象的内容复制到另一个Mat对象中。 函数原型&#xff1a; void cv::Mat::copyTo(OutputArray m) const;void cv::Mat::copyTo(OutputArray m, InputArray mask) const; 参数说明&#xff1a;…

指向未来: 量子纠缠的本质是一个指针

指向未来: 量子纠缠的本质是一个指针 概述基本概念理解量子纠缠PythonJavaC 理解波粒二象性PythonJavaC 理解量子隧穿理解宇宙常量PythonJavaC 概述 量子纠缠 (Quantum Entanglement) 是量子系统重两个或多个粒子间的一种特殊连接, 这种连接使得即使相隔很远, 这些粒子的状态也…

oracle11g的闪回技术-闪回表-时间戳

--数据库闪回表 --1创建表&#xff08;登录模式system&#xff09; CREATE table dept2 as select * from dept;--此语句如果加上where条件可用于工作中数据的临时备份 select * from dept2;--查询新建表信息 --进入sql>set time on 通过时间点闪回 记录弹出的时间点&#…

2024年linux内核开发会是程序员新的风口吗?

前言 众所周知&#xff0c;linux操作系统一直靠着稳定&#xff0c;安全&#xff0c;开源等优势占据着80%以上的服务器市场。小至私人企业&#xff0c;大至世界百强&#xff0c;都离不开它的身影。以至于无论你擅长的是哪门语言&#xff0c;面试的时候都会或多或少的涉及linux的…

物联网网关与plc怎么连接?

物联网网关与plc怎么连接&#xff1f; 物联网是当今社会中最热门的技术之一&#xff0c;而物联网网关则是连接物联网设备与云平台的核心设备之一。物联网网关在连接各种传感器和设备时起着至关重要的作用。而另一种广泛应用于工业控制和自动化领域的设备是可编程逻辑控制器&…

上海亚商投顾:沪指探底回升 大金融板块午后走强

上海亚商投顾前言&#xff1a;无惧大盘涨跌&#xff0c;解密龙虎榜资金&#xff0c;跟踪一线游资和机构资金动向&#xff0c;识别短期热点和强势个股。 一.市场情绪 指昨日探底回升&#xff0c;深成指、创业板指午后跌超1%&#xff0c;尾盘集体拉升翻红&#xff0c;北证50指数涨…

你知道怎么做好接口测试?

前言 谈起软件测试&#xff0c;就不得不说一下接口测试&#xff0c;凡是有功能的软件都离不开接口&#xff0c;没有接口的软件只是一个模具或页面&#xff0c;不具备任何功能。 什么是接口 业内常说的接口一般指两种&#xff1a; API&#xff1a;应用程序编程接口&#xff0c…

【unity】麦克风声音驱动,控制身体做出不同动作

1.在角色对象上挂在animator组件&#xff0c;并将动作控制器与其关联 2.在角色对象上挂在audio source组件。 3.新建voice control脚本&#xff0c;编写代码如下&#xff1a; using System; using System.Collections; using System.Collections.Generic; using UnityEngine;…

vue2 使用vuex状态管理工具 如何配置与搭建。

等我研究研究&#xff0c;下一期给大家出一个后台管理左侧侧边栏如何搭建的。 首先我们先下载一下 vuex包 yarn add vuex3 1.先导入我们需要的 Vue 和 vuex 2.注册vuex 3.创建vuex实例 4.导出store export default store 5.在main.js中导入并挂载到全局。 Vuex如何实…

C++编写、生成、调用so库详解(二)

我们上篇中主要讲了怎么去打包so库 C编写、生成、调用so库详解(一) 这篇我们就来说一些怎么调用so库 目录 1.调用符合JNI标准的so库 2.调用不符合JNI标准的so库 上面说了两种不同类型的so库,我们分别来看一下怎么调用这两种,在调用so库之前,我们先说一下直接调用上面写的C…

全球网络是如何互联的?

1.Internet 在之前的学习中我们知道了Internet和internet的区别&#xff0c;也知道了Internet目前是全球最大的网络&#xff0c;并且由很多规模不同的网络互联而成。到目前已经有超过90个国家接入了Internet&#xff0c;主机超过400万台&#xff0c;可以说Internet是全人类的信…

【Ubuntu18.04安装Labelme】

Ubuntu18.04安装Labelme 1 安装Anaconda并创建conda环境2 安装依赖3 安装Labelme4 安装验证 1 安装Anaconda并创建conda环境 Anaconda3安装教程&#xff1a;https://blog.csdn.net/dally2/article/details/108206234 "ctrlaltt"快捷键打开终端&#xff0c;创建conda…

gateway Redisson接口级别限流解决方案

文章目录 前言1. 计数器算法&#xff08;固定窗口限流器&#xff09;2. 滑动窗口日志限流器3. 漏桶算法&#xff08;Leaky Bucket&#xff09;4. 令牌桶算法&#xff08;Token Bucket&#xff09;5. 限流队列应用场景实现工具 一、Redisson简介二、Redisson限流器的原理三、Red…

利用 ChatGPT 高效搜索:举一反三的思考方式,高效查找解决方案

文章目录 基础思路举一反三基于我的已知推荐 Web 框架系统方案建议 - 让 ChatGPT 推断我的一些微末思考结论 本文只是我的一些尝试&#xff0c;基于 ChatGPT 实现系统化快速搜索某编程语言的特定领域相关包或者基于其他语言类推荐落地方案的尝试。 这篇文章中描述的方式不一定…

ABAP IDOC 2 XML

有个需求&#xff0c;外围系统希望我们给到一个IDOC 记录的样例&#xff0c;但是我们we02中并无法看到 就找了一个demo去直接展示IDOC内容 *&---------------------------------------------------------------------* *& Report Z_IDOC_TO_XML *&------------…

关于前端或者postman传递Date数据测试接口报错

错误&#xff1a; org.apache.ibatis.exceptions.PersistenceException: \r\n### Error querying database. Cause: java.lang.IllegalArgumentException: invalid comparison: java.util.Date and java.lang.String\r\n### Cause: java.lang.IllegalArgumentException: invali…

服务器运维小技巧(一)——如何进行远程协助

服务器运维中经常会遇到一些疑难问题&#xff0c;需要安全工程师&#xff0c;或者其他大神远程协助。很多人会选择使用todesk或者向日葵等一些远控软件。但使用这些软件会存在诸多问题&#xff1a; 双方都需要安装软件&#xff0c;太麻烦需要把服务器的公钥/密码交给对方不知道…