于肖磊修改数据显示
f7b997e5创建于 3月13日历史提交
<template>
    <view v-if="showPopup" class="uni-popup" :class="popupstyle + (isDesktop ? ' fixforpc-z-index' : '')">
        <view @touchstart="touchstart">
            <u-transition key="1" v-if="maskShow" propName="mask" propMode="fade" :propCustomStyle="maskClass" :propDuration="duration" :propShow="showTrans" @click="onTap" />
            <u-transition key="2" :propMode="ani" propName="content" :propCustomStyle="transClass" :propDuration="duration" :propShow="showTrans">
                <view class="pr" :style="'border-radius:' + propRound + 'px'">
                    <view v-if="propCloseType == 'icon' && propCloseable" class="popup-close pa-14 box-border-box" :class="propCloseIconPos" :style="closeIconStyle" @tap="close">
                        <u-icon :propName="propCloseIcon" :propType="propCloseIconType" :propSize="propCloseIconSize + 'rpx'"></u-icon>
                    </view>
                    <view v-if="propTitle != ''" class="pr">
                        <view class="popup-close pa-14 box-border-box flex-row" :class="propTitleBorder ? 'br-b-e' : ''">
                            <view v-if="propTitle != ''" class="flex-1 title">
                                <text class="fw tc">{{ propTitle }}</text>
                            </view>
                        </view>
                    </view>
                    <view v-if="propCloseType == 'text' && propCloseable" class="flex-row jc-sb align-c w pa top-0 pa-14 z-i box-border-box" :style="propWidth">
                        <text class="cr-info" @click="close">取消</text>
                        <view class="pr">
                            <text v-if="propIsCustomBtn" class="inline-block" :style="propCustomBtnStyle" @click="custom_change">{{ propCustomBtnText }}</text>
                            <text class="cr-primary" @click="comfirm">确定</text>
                        </view>
                    </view>
                    <view class="uni-popup__wrapper radius-lg" :style="{ backgroundColor: bg, maxHeight: propHeight }" :class="[popupstyle]" @click="clear">
                        <slot />
                    </view>
                </view>
            </u-transition>
        </view>
        <!-- #ifdef H5 -->
        <keypress v-if="maskShow" @esc="onTap" />
        <!-- #endif -->
    </view>
</template>

<script>
    // #ifdef H5
    import keypress from './keypress.js';
    // #endif

    /**
     * PopUp 弹出层
     * @description 弹出层组件,为了解决遮罩弹层的问题
     * @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
     * 	@value top 顶部弹出
     * 	@value center 中间弹出
     * 	@value bottom 底部弹出
     * 	@value left		左侧弹出
     * 	@value right  右侧弹出
     * 	@value message 消息提示
     * 	@value dialog 对话框
     * 	@value share 底部分享示例
     * @property {Boolean} animation = [true|false] 是否开启动画
     * @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
     * @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
     * @property {String}  backgroundColor 主窗口背景色
     * @property {String}  maskBackgroundColor 蒙版颜色
     * @property {Boolean} safeArea		   是否适配底部安全区
     * @property {Boolean} closeable Boolean | 是否显示关闭图标,默认true
     * @property {String} closeIcon String | 关闭iconfont,默认‘close’ ---还有close-o,或者其他自定义icon
     * @property {String} closeIconType String | 关闭颜色,默认‘6’ 参照组件u-icon
     * @property {String} closeIconPos = [top-left|top-right|top-center|bottom-left|bottom-right|bottom-center] String | 关闭图标位置
     * 	@value top-left 左上角
     * 	@value top-right 右上角
     * 	@value bottom-left 左下角
     * 	@value bottom-right 右下角
     * @property {Number} closeIconSize Number | 关闭图标大小,默认32(单位rpx)
     * @property {Boolean} closeType Boolean | 显示关闭图标还是文本(文本包含确定和取消),默认icon,------icon,text,
     * @property {Boolean} isCustomBtn Boolean | 自定义按钮
     * @property {String} customBtnText String | 自定义按钮文本
     * @property {Object} customBtnStyle Object | 自定义按钮样式
     * @property {String}  title String | 弹窗标题
     * @property {String}  titleBorder String | 弹窗底部边框
     * @property {String}  height String | 弹窗内容高度
     * @property {String}  round String | 弹窗圆角
     * @property {Boolean}  isConfirmClose Boolean | 是否点击确认按钮时主动关闭弹窗
     * @event {Function} change 打开关闭弹窗触发,e={show: false}
     * @event {Function} maskClick 点击遮罩触发
     * @event {Function} callBack 确定按钮回调方法
     * @event {Function} custom_change 自定义按钮回调方法
     */

    export default {
        name: 'uniPopup',
        components: {
            // #ifdef H5
            keypress,
            // #endif
        },
        emits: ['change', 'maskClick', 'callBack', 'callBackCustom'],
        props: {
            // 开启动画
            propAnimation: {
                type: Boolean,
                default: true,
            },
            // 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
            // message: 消息提示 ; dialog : 对话框
            propType: {
                type: String,
                default: 'bottom',
            },
            // maskClick
            propIsMaskClick: {
                type: Boolean,
                default: null,
            },
            // TODO 2 个版本后废弃属性 ,使用 isMaskClick
            propMaskClick: {
                type: Boolean,
                default: null,
            },
            propBackgroundColor: {
                type: String,
                default: 'none',
            },
            propSafeArea: {
                type: Boolean,
                default: true,
            },
            propMaskBackgroundColor: {
                type: String,
                default: 'rgba(0, 0, 0, 0.4)',
            },
            // 是否显示关闭图标
            propCloseable: {
                type: Boolean,
                default: true,
            },
            // icon
            propCloseIcon: {
                type: String,
                default: 'close-line',
            },
            // icon颜色
            propCloseIconType: {
                type: String,
                default: '6',
            },
            // 关闭图标大小
            propCloseIconSize: {
                type: Number,
                default: 32,
            },
            // 关闭图标位置,top-left为左上角,top-right为右上角,bottom-left为左下角,bottom-right为右下角,top-center为外部上部中间位置, bottom-center为外部底部中间位置,
            propCloseIconPos: {
                type: String,
                validator: (value) => ['top-left', 'top-right', 'bottom-left', 'bottom-right', 'top-center', 'bottom-center'].includes(value),
                default: 'top-right',
            },
            propCloseType: {
                type: String,
                default: 'icon',
            },
            propIsCustomBtn: {
                type: Boolean,
                default: false,
            },
            propCustomBtnText: {
                type: String,
                default: '自定义',
            },
            propCustomBtnStyle: {
                type: String,
                default: '',
            },
            // 标题
            propTitle: {
                type: String,
                default: '',
            },
            propTitleBorder: {
                type: Boolean,
                default: false,
            },
            propHeight: {
                type: String,
                default: 'auto',
            },
            propRound: {
                type: Number,
                default: 0,
            },
            propIsConfirmClose: {
                type: Boolean,
                default: true,
            },
            propWidth: {
                type: String,
                default: '',
            },
        },

        watch: {
            /**
             * 监听type类型
             */
            propType: {
                handler: function (type) {
                    if (!this.config[type]) return;
                    this[this.config[type]](true);
                },
                immediate: true,
            },
            isDesktop: {
                handler: function (newVal) {
                    if (!this.config[newVal]) return;
                    this[this.config[this.propType]](true);
                },
                immediate: true,
            },
            /**
             * 监听遮罩是否可点击
             * @param {Object} val
             */
            propMaskClick: {
                handler: function (val) {
                    this.mkclick = val;
                },
                immediate: true,
            },
            propIsMaskClick: {
                handler: function (val) {
                    this.mkclick = val;
                },
                immediate: true,
            },
            // H5 下禁止底部滚动
            showPopup(show) {
                // #ifdef H5
                // fix by mehaotian 处理 h5 滚动穿透的问题
                document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible';
                // #endif
            },
        },
        data() {
            return {
                duration: 300,
                ani: [],
                showPopup: false,
                showTrans: false,
                popupWidth: 0,
                popupHeight: 0,
                config: {
                    top: 'top',
                    bottom: 'bottom',
                    center: 'center',
                    left: 'left',
                    right: 'right',
                    message: 'top',
                    dialog: 'center',
                    share: 'bottom',
                },
                maskClass: {
                    position: 'fixed',
                    bottom: 0,
                    top: '-1000%',
                    left: 0,
                    right: 0,
                    backgroundColor: 'rgba(0, 0, 0, 0.4)',
                },
                transClass: {
                    position: 'fixed',
                    left: 0,
                    right: 0,
                },
                maskShow: true,
                mkclick: true,
                popupstyle: this.isDesktop ? 'fixforpc-top' : 'top',
            };
        },
        computed: {
            isDesktop() {
                return this.popupWidth >= 500 && this.popupHeight >= 500;
            },
            bg() {
                if (this.propBackgroundColor === '' || this.propBackgroundColor === 'none') {
                    return '#fff';
                }
                return this.propBackgroundColor;
            },
            // transition固定样式
            transitionFixedStyle() {
                let style = {
                    backgroundColor: this.bgColor,
                    zIndex: this.zIndex,
                };
                switch (this.mode) {
                    case 'top':
                        style['borderBottomLeftRadius'] = this.propRound + 'px';
                        style['borderBottomRightRadius'] = this.propRound + 'px';
                        break;
                    case 'bottom':
                        style['borderTopLeftRadius'] = this.propRound + 'px';
                        style['borderTopRightRadius'] = this.propRound + 'px';
                        break;
                    case 'left':
                        style['borderTopRightRadius'] = this.propRound + 'px';
                        style['borderBottomRightRadius'] = this.propRound + 'px';
                        break;
                    case 'right':
                        style['borderTopLeftRadius'] = this.propRound + 'px';
                        style['borderBottomLeftRadius'] = this.propRound + 'px';
                        break;
                    case 'center':
                        style['borderRadius'] = this.propRound + 'px';
                        break;
                }

                return style;
            },
            // 关闭Icon样式
            closeIconStyle() {
                let style = {
                    zIndex: 999,
                };
                let posSize = 28;
                switch (this.propCloseIconPos) {
                    case 'top-left':
                        style['position'] = 'absolute';
                        style['top'] = '0rpx';
                        style['left'] = '0rpx';
                        break;
                    case 'top-right':
                        style['position'] = 'absolute';
                        style['top'] = '0rpx';
                        style['right'] = '0rpx';
                        break;
                    case 'top-center':
                        style['position'] = 'absolute';
                        style['top'] = -(posSize * 4) + 'rpx';
                        style['left'] = '0rpx';
                        style['right'] = '0rpx';
                        break;
                    case 'bottom-left':
                        style['position'] = 'absolute';
                        style['bottom'] = '0rpx';
                        style['left'] = '0rpx';
                        break;
                    case 'bottom-right':
                        style['position'] = 'absolute';
                        style['bottom'] = '0rpx';
                        style['right'] = '0rpx';
                        break;
                    case 'bottom-center':
                        style['position'] = 'absolute';
                        style['bottom'] = -(posSize * 4) + 'rpx';
                        style['left'] = '0rpx';
                        style['right'] = '0rpx';
                        break;
                    default:
                        if (this.propTitle != '') {
                            style['position'] = 'absolute';
                            style['top'] = '0rpx';
                            style['right'] = '0rpx';
                        }
                        break;
                }
                let result = '';
                Object.keys(style).forEach(key => {
                    result += `${key}: ${style[key]};`;
                });
                return result;
            },
        },
        mounted() {
            const fixSize = () => {
                const { windowWidth, windowHeight, windowTop, safeArea, screenHeight, safeAreaInsets } = uni.getSystemInfoSync();
                this.popupWidth = windowWidth;
                this.popupHeight = windowHeight + (windowTop || 0);
                // TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
                if (safeArea && this.propSafeArea) {
                    // #ifdef MP-WEIXIN
                    this.safeAreaInsets = screenHeight - safeArea.bottom;
                    // #endif
                    // #ifndef MP-WEIXIN
                    this.safeAreaInsets = safeAreaInsets.bottom;
                    // #endif
                } else {
                    this.safeAreaInsets = 0;
                }
            };
            fixSize();
            // #ifdef H5
            // window.addEventListener('resize', fixSize)
            // this.$once('hook:beforeDestroy', () => {
            // 	window.removeEventListener('resize', fixSize)
            // })
            // #endif
            // #ifdef APP-NVUE
            // 修复nvue 遮罩层位置问题
            const data = uni.getWindowInfo();
            this.maskClass.top = - data.windowHeight + 'px;';
            // #endif
        },
        // #ifndef VUE3
        // TODO vue2
        destroyed() {
            this.setH5Visible();
        },
        // #endif
        // #ifdef VUE3
        // TODO vue3
        unmounted() {
            this.setH5Visible();
        },
        // #endif
        created() {
            // this.mkclick =  this.propIsMaskClick || this.propMaskClick
            if (this.propIsMaskClick === null && this.propMaskClick === null) {
                this.mkclick = true;
            } else {
                this.mkclick = this.propIsMaskClick != null ? this.propIsMaskClick : this.propMaskClick;
            }
            if (this.propAnimation) {
                this.duration = 300;
            } else {
                this.duration = 0;
            }
            // TODO 处理 message 组件生命周期异常的问题
            this.messageChild = null;
            // TODO 解决头条冒泡的问题
            this.clearPropagation = false;
            this.maskClass.backgroundColor = this.propMaskBackgroundColor;
        },
        methods: {
            setH5Visible() {
                // #ifdef H5
                // fix by mehaotian 处理 h5 滚动穿透的问题
                document.getElementsByTagName('body')[0].style.overflow = 'visible';
                // #endif
            },
            /**
             * 公用方法,不显示遮罩层
             */
            closeMask() {
                this.maskShow = false;
            },
            /**
             * 公用方法,遮罩层禁止点击
             */
            disableMask() {
                this.mkclick = false;
            },
            // TODO nvue 取消冒泡
            clear(e) {
                // #ifndef APP-NVUE
                e.stopPropagation();
                // #endif
                this.clearPropagation = true;
            },

            open(direction) {
                // fix by mehaotian 处理快速打开关闭的情况
                if (this.showPopup) {
                    clearTimeout(this.timer);
                    this.showPopup = false;
                }
                let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share'];
                if (!(direction && innerType.indexOf(direction) != -1)) {
                    direction = this.propType;
                }
                if (!this.config[direction]) {
                    return;
                }
                this[this.config[direction]]();
                this.$emit('change', {
                    show: true,
                    type: direction,
                });
            },
            close(type) {
                this.showTrans = false;
                this.$emit('change', {
                    show: false,
                    type: this.propType,
                });
                this.$emit('close')
                clearTimeout(this.timer);
                // // 自定义关闭事件
                // this.customOpen && this.customClose()
                this.timer = setTimeout(() => {
                    this.showPopup = false;
                }, 300);
            },
            // TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
            touchstart() {
                this.clearPropagation = false;
            },

            onTap() {
                if (this.clearPropagation) {
                    // fix by mehaotian 兼容 nvue
                    this.clearPropagation = false;
                    return;
                }
                this.$emit('maskClick');
                if (!this.mkclick) return;
                this.close();
            },
            /**
             * 顶部弹出样式处理
             */
            top(type) {
                this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top';
                this.ani = ['slide-top'];
                this.transClass = {
                    position: 'fixed',
                    left: 0,
                    right: 0,
                    backgroundColor: this.bg,
                };
                // TODO 兼容 type 属性 ,后续会废弃
                if (type) return;
                this.showPopup = true;
                this.showTrans = true;
                this.$nextTick(() => {
                    if (this.messageChild && this.propType === 'message') {
                        this.messageChild.timerClose();
                    }
                });
            },
            /**
             * 底部弹出样式处理
             */
            bottom(type) {
                this.popupstyle = 'bottom';
                this.ani = ['slide-bottom'];
                this.transClass = {
                    position: 'fixed',
                    left: 0,
                    right: 0,
                    bottom: 0,
                    paddingBottom: this.safeAreaInsets + 'px',
                    backgroundColor: this.bg,
                };
                // TODO 兼容 type 属性 ,后续会废弃
                if (type) return;
                this.showPopup = true;
                this.showTrans = true;
            },
            /**
             * 中间弹出样式处理
             */
            center(type) {
                this.popupstyle = 'center';
                this.ani = ['zoom-out', 'fade'];
                this.transClass = {
                    position: 'fixed',
                    /* #ifndef APP-NVUE */
                    display: 'flex',
                    flexDirection: 'column',
                    /* #endif */
                    bottom: 0,
                    left: 0,
                    right: 0,
                    top: 0,
                    justifyContent: 'center',
                    alignItems: 'center',
                };
                // TODO 兼容 type 属性 ,后续会废弃
                if (type) return;
                this.showPopup = true;
                this.showTrans = true;
            },
            left(type) {
                this.popupstyle = 'left';
                this.ani = ['slide-left'];
                this.transClass = {
                    position: 'fixed',
                    left: 0,
                    bottom: 0,
                    top: 0,
                    backgroundColor: this.bg,
                    /* #ifndef APP-NVUE */
                    display: 'flex',
                    flexDirection: 'column',
                    /* #endif */
                };
                // TODO 兼容 type 属性 ,后续会废弃
                if (type) return;
                this.showPopup = true;
                this.showTrans = true;
            },
            right(type) {
                this.popupstyle = 'right';
                this.ani = ['slide-right'];
                this.transClass = {
                    position: 'fixed',
                    bottom: 0,
                    right: 0,
                    top: 0,
                    backgroundColor: this.bg,
                    /* #ifndef APP-NVUE */
                    display: 'flex',
                    flexDirection: 'column',
                    /* #endif */
                };
                // TODO 兼容 type 属性 ,后续会废弃
                if (type) return;
                this.showPopup = true;
                this.showTrans = true;
            },
            comfirm() {
                if (this.propIsConfirmClose) {
                    this.close();
                }
                this.$emit('callBack');
            },
            // 自定义按钮点击回调
            custom_change() {
                if (this.propIsConfirmClose) {
                    this.close();
                }
                this.$emit('callBackCustom');
            },
        },
    };
</script>
<style lang="scss" scoped>
    .uni-popup {
        position: fixed;
        max-width: 1600rpx;
        margin: 0 auto;
        /* #ifndef APP-NVUE */
        z-index: 99;

        /* #endif */
        &.top,
        &.left,
        &.right {
            /* #ifdef H5 */
            top: var(--window-top);
            /* #endif */
            /* #ifndef H5 */
            top: 0;
            /* #endif */
        }

        &.top {
            .popup-close {
                display: none;
            }
        }
        &.center,
        &.bottom,
        &.left {
            .popup-close {
                display: flex;
                justify-content: flex-end;
            }
        }

        &.right {
            .popup-close {
                display: flex;
                justify-content: flex-start;
            }
        }

        .uni-popup__wrapper {
            /* #ifndef APP-NVUE */
            display: block;
            /* #endif */
            position: relative;
            overflow-y: auto;

            /* iphonex 等安全区设置,底部安全区适配 */
            /* #ifndef APP-NVUE */
            // padding-bottom: constant(safe-area-inset-bottom);
            // padding-bottom: env(safe-area-inset-bottom);
            /* #endif */
            &.left,
            &.right {
                /* #ifdef H5 */
                padding-top: var(--window-top);
                /* #endif */
                /* #ifndef H5 */
                padding-top: 0;
                /* #endif */
                flex: 1;
            }
        }

        .popup-close {
            padding: 28rpx;
            &.top-center,
            &.bottom-center {
                justify-content: center;
            }
        }

        .title {
            width: 100%;
            display: flex;
            flex-direction: row;
            justify-content: center;
            align-items: center;
        }
    }

    .fixforpc-z-index {
        /* #ifndef APP-NVUE */
        z-index: 999;
        /* #endif */
    }

    .fixforpc-top {
        top: 0;
    }
    .fw {
        font-weight: 700;
    }
    .tc {
        text-align: center;
    }
</style>