You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

590 lines
15 KiB
Vue

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<template>
<view
v-if="showPopup"
class="uni-popup"
:class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']"
:style="[{ zIndex: zIndex }]"
@touchmove.stop.prevent="clear"
>
<view @touchstart="touchstart">
<uni-transition
key="1"
v-if="maskShow"
name="mask"
mode-class="fade"
:styles="maskClass"
:duration="duration"
:show="showTrans"
@click="onTap"
/>
<uni-transition
key="2"
:mode-class="ani"
name="content"
:styles="{ ...transClass, ...borderRadius }"
:duration="duration"
:show="showTrans"
@click="onTap"
>
<view
v-if="showPopup"
class="uni-popup__wrapper"
:style="[{ backgroundColor: bg }, borderRadius]"
:class="[popupstyle]"
@click="clear"
>
<uni-icons
v-if="showClose"
class="close-icon"
color="#F6F6F6"
type="closeempty"
size="32"
@click="close"
></uni-icons>
<slot />
</view>
</uni-transition>
</view>
<!-- #ifdef H5 -->
<keypress v-if="maskShow" @esc="onTap" />
<!-- #endif -->
</view>
<!-- #ifdef MP -->
<view v-else style="display: none">
<slot></slot>
</view>
<!-- #endif -->
</template>
<script>
// #ifdef H5
import keypress from './keypress.js';
// #endif
/**
* PopUp 弹出层
* @description 弹出层组件,为了解决遮罩弹层的问题
* @tutorial https://ext.dcloud.net.cn/plugin?id=329
* @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 是否适配底部安全区
* @event {Function} change 打开关闭弹窗触发e={show: false}
* @event {Function} maskClick 点击遮罩触发
*/
import sheep from '@/sheep';
export default {
name: 'SuPopup',
components: {
// #ifdef H5
keypress,
// #endif
},
emits: ['change', 'maskClick', 'close'],
props: {
// 开启状态
show: {
type: Boolean,
default: false,
},
// 顶部,底部时有效
space: {
type: Number,
default: 0,
},
// 默认圆角
round: {
type: [String, Number],
default: 0,
},
// 是否显示关闭
showClose: {
type: Boolean,
default: false,
},
// 开启动画
animation: {
type: Boolean,
default: true,
},
// 弹出层类型可选值top: 顶部弹出层bottom底部弹出层center全屏弹出层
// message: 消息提示 ; dialog : 对话框
type: {
type: String,
default: 'bottom',
},
// maskClick
isMaskClick: {
type: Boolean,
default: null,
},
// TODO 2 个版本后废弃属性 ,使用 isMaskClick
maskClick: {
type: Boolean,
default: null,
},
// 可设置none
backgroundColor: {
type: String,
default: '#ffffff',
},
backgroundImage: {
type: String,
default: '',
},
safeArea: {
type: Boolean,
default: true,
},
maskBackgroundColor: {
type: String,
default: 'rgba(0, 0, 0, 0.4)',
},
zIndex: {
type: [String, Number],
default: 10075,
},
},
watch: {
show: {
handler: function (newValue, oldValue) {
if (typeof oldValue === 'undefined' && !newValue) {
return;
}
if (newValue) {
this.open();
} else {
this.close();
}
},
immediate: true,
},
/**
* 监听type类型
*/
type: {
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.type]](true);
},
immediate: true,
},
/**
* 监听遮罩是否可点击
* @param {Object} val
*/
maskClick: {
handler: function (val) {
this.mkclick = val;
},
immediate: true,
},
isMaskClick: {
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 {
sheep,
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: 0,
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.backgroundColor === '' || this.backgroundColor === 'none') {
return 'transparent';
}
return this.backgroundColor;
},
borderRadius() {
if (this.round) {
if (this.type === 'bottom') {
return {
'border-top-left-radius': parseFloat(this.round) + 'px',
'border-top-right-radius': parseFloat(this.round) + 'px',
};
}
if (this.type === 'center') {
return {
'border-top-left-radius': parseFloat(this.round) + 'px',
'border-top-right-radius': parseFloat(this.round) + 'px',
'border-bottom-left-radius': parseFloat(this.round) + 'px',
'border-bottom-right-radius': parseFloat(this.round) + 'px',
};
}
if (this.type === 'top') {
return {
'border-bottom-left-radius': parseFloat(this.round) + 'px',
'border-bottom-right-radius': parseFloat(this.round) + 'px',
};
}
}
},
},
mounted() {
const fixSize = () => {
const { windowWidth, windowHeight, windowTop, safeArea, screenHeight, safeAreaInsets } =
sheep.$platform.device;
this.popupWidth = windowWidth;
this.popupHeight = windowHeight + (windowTop || 0);
// TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
if (safeArea && this.safeArea) {
// #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
},
// #ifndef VUE3
// TODO vue2
destroyed() {
this.setH5Visible();
},
// #endif
// #ifdef VUE3
// TODO vue3
unmounted() {
this.setH5Visible();
},
// #endif
created() {
// this.mkclick = this.isMaskClick || this.maskClick
if (this.isMaskClick === null && this.maskClick === null) {
this.mkclick = true;
} else {
this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick;
}
if (this.animation) {
this.duration = 300;
} else {
this.duration = 0;
}
// TODO 处理 message 组件生命周期异常的问题
this.messageChild = null;
// TODO 解决头条冒泡的问题
this.clearPropagation = false;
this.maskClass.backgroundColor = this.maskBackgroundColor;
},
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.type;
}
if (!this.config[direction]) {
console.error('缺少类型:', 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.type,
});
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,
top: this.space + 'px',
backgroundColor: this.bg,
};
// TODO 兼容 type 属性 ,后续会废弃
if (type) return;
this.showPopup = true;
this.showTrans = true;
this.$nextTick(() => {
if (this.messageChild && this.type === '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 + this.space + '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;
},
},
};
</script>
<style lang="scss">
// icon
.close-icon {
position: absolute;
left: 50%;
transform: translateX(-50%);
bottom: -80rpx;
z-index: 100;
}
.uni-popup {
position: fixed;
/* #ifndef APP-NVUE */
z-index: 99;
/* #endif */
&.top,
&.left,
&.right {
/* #ifdef H5 */
top: var(--window-top);
/* #endif */
/* #ifndef H5 */
top: 0;
/* #endif */
}
.uni-popup__wrapper {
/* #ifndef APP-NVUE */
display: block;
/* #endif */
position: relative;
background: v-bind(backgroundImage) no-repeat;
background-size: 100% 100%;
/* 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;
}
}
}
.fixforpc-z-index {
/* #ifndef APP-NVUE */
z-index: 999;
/* #endif */
}
.fixforpc-top {
top: 0;
}
</style>