296 lines
5.0 KiB
Vue
296 lines
5.0 KiB
Vue
<template>
|
|
<view
|
|
v-if="visible"
|
|
:class="[
|
|
'cl-popup__wrapper',
|
|
`cl-popup__wrapper--${direction}`,
|
|
`is-${status ? 'open' : 'close'}`,
|
|
{
|
|
'is-modal': modal,
|
|
},
|
|
]"
|
|
:style="{
|
|
zIndex,
|
|
}"
|
|
@touchmove.stop.prevent
|
|
>
|
|
<!-- 遮罩层 -->
|
|
<view
|
|
class="cl-popup__modal"
|
|
:style="{
|
|
background: modalBackground,
|
|
}"
|
|
@tap="modalClose"
|
|
v-if="modal"
|
|
></view>
|
|
|
|
<!-- 内容 -->
|
|
<view
|
|
class="cl-popup"
|
|
:style="{ height, width, backgroundColor, borderRadius: parseRpx(borderRadius) }"
|
|
>
|
|
<view class="cl-popup__header" v-if="title && showHeader">
|
|
<slot name="header">{{ title }}</slot>
|
|
</view>
|
|
|
|
<view class="cl-popup__container" :style="{ padding: parseRpx(padding), paddingTop }">
|
|
<slot></slot>
|
|
</view>
|
|
|
|
<view class="cl-popup__close" v-if="status && showCloseBtn" @tap="close">
|
|
<text class="cl-icon-close"></text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { computed, defineComponent, ref, watch, type PropType } from "vue";
|
|
import { parseRpx } from "/@/cool/utils";
|
|
import { router } from "/@/cool";
|
|
|
|
const { statusBarHeight = 0 } = uni.getSystemInfoSync();
|
|
|
|
let id = 1;
|
|
|
|
export default defineComponent({
|
|
name: "cl-popup",
|
|
|
|
props: {
|
|
// 是否可见
|
|
modelValue: Boolean,
|
|
// 标题
|
|
title: String,
|
|
// 是否显示头部
|
|
showHeader: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
// 弹出方向
|
|
direction: {
|
|
type: String as PropType<"top" | "right" | "bottom" | "center" | "left">,
|
|
default: "center",
|
|
},
|
|
// 弹出框宽度
|
|
size: {
|
|
type: [String, Number],
|
|
default: "auto",
|
|
},
|
|
// 內间距
|
|
padding: {
|
|
type: [String, Number],
|
|
default: 32,
|
|
},
|
|
// 弹出框圆角
|
|
borderRadius: [Number, Array, String],
|
|
// 显示关闭按钮
|
|
showCloseBtn: Boolean,
|
|
// 背景色
|
|
backgroundColor: {
|
|
type: String,
|
|
default: "#fff",
|
|
},
|
|
// 是否显示遮罩层
|
|
modal: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
// 遮罩层背景色
|
|
modalBackground: {
|
|
type: String,
|
|
default: "rgba(0, 0, 0, 0.4)",
|
|
},
|
|
// 点击遮罩层是否关闭
|
|
closeOnClickModal: {
|
|
type: Boolean,
|
|
default: true,
|
|
},
|
|
// 层级
|
|
zIndex: {
|
|
type: Number,
|
|
default: 600,
|
|
},
|
|
},
|
|
|
|
emits: ["update:modelValue", "open", "opened", "close", "closed"],
|
|
|
|
setup(props, { emit }) {
|
|
// 是否可见
|
|
const visible = ref(false);
|
|
|
|
// 是否已打开
|
|
const isOpened = ref(false);
|
|
|
|
// 是否已关闭
|
|
const isClosed = ref(true);
|
|
|
|
// 动画状态
|
|
const status = ref(false);
|
|
|
|
// 层级
|
|
const zIndex = ref(0);
|
|
|
|
// 计时器
|
|
let timer: any;
|
|
|
|
// 是否聚焦
|
|
const isFocus = computed(() => {
|
|
// #ifdef MP
|
|
return isOpened.value;
|
|
// #endif
|
|
|
|
// #ifndef MP
|
|
return true;
|
|
// #endif
|
|
});
|
|
|
|
// 高
|
|
const height = computed(() => {
|
|
switch (props.direction) {
|
|
case "top":
|
|
case "bottom":
|
|
return parseRpx(props.size);
|
|
case "left":
|
|
case "right":
|
|
return "100%";
|
|
}
|
|
});
|
|
|
|
// 宽
|
|
const width = computed(() => {
|
|
switch (props.direction) {
|
|
case "top":
|
|
case "bottom":
|
|
return "100%";
|
|
case "left":
|
|
case "right":
|
|
case "center":
|
|
return parseRpx(props.size);
|
|
}
|
|
});
|
|
|
|
// 顶部距离
|
|
const paddingTop = computed(() => {
|
|
if (["left", "right", "top"].includes(props.direction)) {
|
|
let h = 0;
|
|
|
|
if (router.info()?.isCustomNavbar) {
|
|
h += statusBarHeight;
|
|
} else {
|
|
// #ifdef H5
|
|
h += 44 + statusBarHeight;
|
|
// #endif
|
|
}
|
|
|
|
let [t] = parseRpx(props.padding).split(" ");
|
|
|
|
if (t == "0rpx") {
|
|
t = "0px";
|
|
}
|
|
|
|
return `calc(${h}px + ${t})`;
|
|
} else {
|
|
return 1;
|
|
}
|
|
});
|
|
|
|
// 打开
|
|
function open() {
|
|
// 层级
|
|
zIndex.value = props.zIndex + id++;
|
|
|
|
if (!visible.value) {
|
|
// 显示内容
|
|
visible.value = true;
|
|
|
|
// 未打开
|
|
isClosed.value = false;
|
|
|
|
emit("update:modelValue", true);
|
|
emit("open");
|
|
|
|
clearTimeout(timer);
|
|
|
|
timer = setTimeout(() => {
|
|
// 开始动画
|
|
status.value = true;
|
|
|
|
// 等待动画结束
|
|
timer = setTimeout(() => {
|
|
// 已打开
|
|
isOpened.value = true;
|
|
emit("opened");
|
|
}, 350);
|
|
}, 50);
|
|
}
|
|
}
|
|
|
|
// 关闭
|
|
function close() {
|
|
if (status.value) {
|
|
const done = () => {
|
|
isOpened.value = false;
|
|
|
|
// 关闭动画
|
|
status.value = false;
|
|
emit("close");
|
|
|
|
clearTimeout(timer);
|
|
|
|
timer = setTimeout(() => {
|
|
// 隐藏内容
|
|
visible.value = false;
|
|
emit("update:modelValue", false);
|
|
|
|
// 已关闭
|
|
isClosed.value = true;
|
|
emit("closed");
|
|
}, 300);
|
|
};
|
|
|
|
done();
|
|
}
|
|
}
|
|
|
|
// 遮罩层关闭
|
|
function modalClose() {
|
|
if (!props.closeOnClickModal) {
|
|
return false;
|
|
}
|
|
|
|
close();
|
|
}
|
|
|
|
watch(
|
|
() => props.modelValue,
|
|
(val: boolean) => {
|
|
if (val) {
|
|
open();
|
|
} else {
|
|
close();
|
|
}
|
|
},
|
|
{
|
|
immediate: true,
|
|
},
|
|
);
|
|
|
|
return {
|
|
visible,
|
|
isOpened,
|
|
isClosed,
|
|
isFocus,
|
|
status,
|
|
height,
|
|
width,
|
|
paddingTop,
|
|
zIndex,
|
|
parseRpx,
|
|
open,
|
|
close,
|
|
modalClose,
|
|
};
|
|
},
|
|
});
|
|
</script>
|