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

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

在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控制

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

关键信息基础设施安全相关材料汇总

文章目录 前言一、法律(1)《中华人民共和国国家安全法》(2)《中华人民共和国网络安全法》(3) 《中华人民共和国密码法》(4)《中华人民共和国数据安全法》(5) 《中华人民共和国个人信息保护法》二、行政法规(6)《中华人民共和国保守国家秘密法实施条例》(7) 《关键信息基础设施安…

解决Python安装库时出现的Requirement already satisfied问题

uirement already satisfied的问题当我们用pip install 库名时&#xff0c;出现了下面 Requirement already satisfied WARNING: Ignoring invalid distribution -ip 的问题 对于这样的问题&#xff0c;解决办法就是在 pip install 后加 - -target你所要添加的库文件地址(注意…

python2实现数据库表定时全量同步sftp

python2实现数据库表定时全量同步sftp 需求 周边系统需要通过sftp接口&#xff0c;将本系统数据库的8张表吐给sftp&#xff0c;文件名为txt,提供的字段用#号分隔&#xff08;逗号存在分隔不开的情况&#xff09;&#xff0c;8张表采用全量每天同步。 环境 操作系统centos7.…

pandas字符串操作(下)

目录 数据预览&#xff1a; 七、contains判断字符串里是否包含某字符 1.需求&#xff1a; 2.讲解 3.效果展示 八、startswith/endswith判断是否以某个字开头或结尾 1.需求&#xff1a; 2.讲解 3.效果展示 九、repeat将字符串进行重复 1.需求&#xff1a; 2.讲解 3.…

python的tabulate包在命令行下输出表格不对齐

用tabulate可以在命令行下输出表格。 from tabulate import tabulate# 定义表头 headers [列1, 列2, 列3]# 每行的内容 rows [] rows.append((张三,数学,英语)) rows.append((李四,信息科技,数学))# 使用 tabulate 函数生成表格 output tabulate(rows, headersheaders, tab…

数组中第K个最大元素(算法村第十关白银挑战)

215. 数组中的第K个最大元素 - 力扣&#xff08;LeetCode&#xff09; 给定整数数组 nums 和整数 k&#xff0c;请返回数组中第 **k** 个最大的元素。 请注意&#xff0c;你需要找的是数组排序后的第 k 个最大的元素&#xff0c;而不是第 k 个不同的元素。 你必须设计并实现…

Intel 显卡小结

Intel显卡从很早前的i740&#xff08;98年2月&#xff0c;4或8M现存&#xff0c;i740的RAMDAC为203MHZ,支持2X AGP规格,核心频率80MHZ,采用8M速度为100MHZ的SGRAM显存,像素填充率为55MPixels/s,三角形生成速度为500K Trianglws/s,支持DVD解压&#xff0c;AGP 2X&#xff0c;同时…

Qt框架学习 --- CTK编译(Qt5.15.2+vs2019+cmake)

系列文章目录 第二章 CTK的测试demo https://blog.csdn.net/yonug1107716573/article/details/135527289 文章目录 系列文章目录前言一、准备工作二、编译步骤1.修改文件2.编译CTK2.1 准备2.2 cmake界面配置2.3 配置编译器2.4 编译的配置设置2.5 选择需要编译的模块2.6 生成2.…

九、K8S-label和label Selector

label和label selector 标签和标签选择器 1、label 标签&#xff1a; 一个label就是一个key/value对 label 特性&#xff1a; label可以被附加到各种资源对象上一个资源对象可以定义任意数量的label同一个label可以被添加到任意数量的资源上 2、label selector 标签选择器 L…

Jmeter多种定时器实现方法解析

1、固定定时器&#xff08;Constant Timer&#xff09; 用法(场景)&#xff1a;更真实的模拟用户场景&#xff0c;需要设置等待时间&#xff0c;或是等待上一个请求的时间才执行&#xff0c;给 sampler 之间的思考时间 备注&#xff1a;如果需要每个步骤均延迟&#xff0c;则…

Unity关于纹理图片格式带来的内存问题和对预制体批量格式和大小减半处理

我们经常会遇到内存问题&#xff0c;这次就是遇到很多图片的默认格式被改成了RGB32&#xff0c;导致Android打包后运行内存明显增加。 发生了什么 打包Android后&#xff0c;发现经常崩溃&#xff0c;明显内存可能除了问题&#xff0c;看了内存后发现了问题。 见下图&#xf…

Docker 容器连接

Docker 容器连接 前面我们实现了通过网络端口来访问运行在 docker 容器内的服务。 容器中可以运行一些网络应用&#xff0c;要让外部也可以访问这些应用&#xff0c;可以通过 -P 或 -p 参数来指定端口映射。 下面我们来实现通过端口连接到一个 docker 容器。 网络端口映射 …

开发者的API利器:Apipost

在当今的数字化时代&#xff0c;数据流通是推动社会进步的关键因素之一。其中&#xff0c;API&#xff08;应用编程接口&#xff09;已经成为跨平台数据交互的标准。然而&#xff0c;API开发和管理并非易事&#xff0c;Apipost一体化研发协作赋能平台&#xff0c;支持从API设计…

STM32---基本定时器(含源码)小白可入

写在前面&#xff1a;定时器是STM32中一个十分重要的外设&#xff0c;并且在STM32中具有多个定时器。定时器的包括基本定时器、通用定时器以及高级控制定时器&#xff0c;这些定时器相关独立&#xff0c;不共享任何资源。当然&#xff0c;其难易程度也是逐渐增加的&#xff0c;…

HarmonyOS NEXT鸿蒙星河版发布

1月18日,在深圳举行的“鸿蒙生态千帆启航仪式”上,华为常务董事、终端BG CEO余承东宣布HarmonyOS NEXT鸿蒙星河版面向开发者开放申请。鸿蒙星河版将实现原生精致、原生易用、原生流畅、原生安全、原生智能、原生互联6大极致原生体验。 并且,华为在 1 月 15 日开启了HarmonyO…

如何快速压缩图片至100kb以下?学会方法只需一分钟!

怎么压缩图片小于100kb?我们在网站上传照片时&#xff0c;会发现系统提示超过100k的照片无法上传&#xff0c;很多小伙伴都不知道怎么处理&#xff0c;有人会使用图片裁剪的方式去让图片变小&#xff0c;其实最简单的就是压缩图片&#xff08;https://www.yasuotu.com&#xf…

PHP Fatal error: Unparenthesized `a ? b : c ? d : e` is not supported.

这个错误是关于三元运算符的错误 这个错误在php8.0以下的版本好像是没问题呢 PHP Fatal error: Unparenthesized a ? b : c ? d : e is not supported. Use either (a ? b : c) ? d : e or a ? b : (c ? d : e) in /cangku/app/common.php on line 57 这个问题是 程…

WebX代码和接口文档自动生成器

朋友最近自己搞了一个代码和接口文档自动生成平台WebX。大家有兴趣可以体验一下。 平台介绍地址&#xff1a;WebX平台

快速完成IP地址免域名HTTPS改造,轻松应对商密测评整改

10分钟完成基于IP地址免域名的商密HTTPS改造&#xff0c;让商密测评整改不再头疼。 一般选择免费SSL证书单域 注意&#xff1a;申请过程中需要保存RSA和SM2的私钥。 免费SSL证书单域 主域名&#xff1a;8.141.89.22 证书编号(Order #): 1956635926 以下命令需root用户操作 切…

【UE Niagara】制作传送门_Part1

目录 效果 步骤 一、创建Niagara系统 二、制作外圈部分 2.1 修改粒子形成的形状 2.2 给粒子添加作用力 三、制作中心部分 3.1 添加新的发射器 3.2 制作中心平面的材质 效果 步骤 一、创建Niagara系统 新建一个Niagara系统 选择“Simple Sprite Burst”模板 这里命…