| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- <template>
- <view
- :class="[
- 'nana-popup',
- position,
- showAnimState ? 'show' : '',
- mask ? 'stop' : '',
- mask ? (show2 ? 'show2' : '') : 'no-mask',
- ]"
- :style="{
- ...selectStyleType(position, 'bottom', {
- center: {
- justifyContent: 'center',
- alignItems: 'center',
- },
- top: {
- justifyContent: 'flex-start',
- alignItems: 'center',
- },
- bottom: {
- justifyContent: 'flex-end',
- alignItems: 'center',
- },
- left: {
- alignItems: 'flex-start',
- justifyContent: 'center',
- },
- right: {
- alignItems: 'flex-end',
- justifyContent: 'center',
- },
- }),
- top: inset[0] ? `${themeContext.resolveThemeSize(inset[0])}` : undefined,
- right: inset[1] ? `${themeContext.resolveThemeSize(inset[1])}` : undefined,
- bottom: inset[2] ? `${themeContext.resolveThemeSize(inset[2])}` : undefined,
- left: inset[3] ? `${themeContext.resolveThemeSize(inset[3])}` : undefined,
- }"
- >
- <view
- class="nana-popup-mask"
- :style="{
- backgroundColor: mask ? themeContext.resolveThemeColor(maskColor) : '',
- transitionDuration: `${duration}ms`,
- }"
- @mousedown.stop="handleClose"
- @touchstart.stop="handleClose"
- @click.stop="handleClose"
- >
- </view>
- <view
- v-if="show2"
- :class="[ 'nana-popup-content', position] "
- :style="{
- ...selectStyleType(position, 'bottom', {
- center: {
- flexDirection: 'row',
- borderRadius: radius,
- },
- top: {
- borderBottomLeftRadius: radius,
- borderBottomRightRadius: radius,
- width: '100%',
- minHeight: dialogSize,
- },
- bottom: {
- borderTopLeftRadius: radius,
- borderTopRightRadius: radius,
- width: '100%',
- minHeight: dialogSize,
- },
- left: {
- borderTopRightRadius: radius,
- borderBottomRightRadius: radius,
- height: '100%',
- minWidth: dialogSize,
- },
- right: {
- borderTopLeftRadius: radius,
- borderBottomLeftRadius: radius,
- height: '100%',
- minWidth: dialogSize,
- },
- }),
- backgroundColor: themeContext.resolveThemeColor(backgroundColor),
- margin: `${themeContext.resolveThemeSize(margin[0])} ${themeContext.resolveThemeSize(margin[1])} ${themeContext.resolveThemeSize(margin[2])} ${themeContext.resolveThemeSize(margin[3])}`,
- ...innerStyle,
- }"
- @click.stop
- >
- <SafeAreaPadding
- :top="safeArea && (position === 'top' || position === 'left' || position === 'right')"
- :bottom="safeArea && (position === 'bottom' || position === 'left' || position === 'right')"
- >
- <PopupTitle
- v-if="position !== 'top'"
- :closeable="closeable"
- :closeIcon="closeIcon"
- :closeIconSize="closeIconSize"
- :closeIconPosition="closeIconPosition"
- :top="true"
- @close="doClose"
- />
- <slot />
- <PopupTitle
- v-if="position === 'top'"
- :closeable="closeable"
- :closeIcon="closeIcon"
- :closeIconSize="closeIconSize"
- :closeIconPosition="closeIconPosition"
- @close="doClose"
- />
- </SafeAreaPadding>
- </view>
- </view>
- </template>
- <script setup lang="ts">
- import { computed, ref, watch } from 'vue';
- import { useTheme, type ViewStyle } from '../theme/ThemeDefine';
- import { selectStyleType } from '../theme/ThemeTools';
- import { SimpleDelay } from '@imengyu/imengyu-utils';
- import PopupTitle from './PopupTitle.vue';
- import SafeAreaPadding from '../layout/space/SafeAreaPadding.vue';
- /**
- * Popup 的显示位置
- */
- export type PopupPosition = 'center'|'top'|'bottom'|'left'|'right';
- /**
- * Popup 关闭按钮显示位置
- */
- export type PopupCloseButtonPosition = 'left'|'right';
- /**
- * Popup 组件属性
- */
- export interface PopupProps {
- /**
- * 是否显示当前弹窗
- */
- show: boolean;
- /**
- * 弹出层圆角
- */
- round?: boolean;
- /**
- * 是否可以点击遮罩关闭当前弹出层,同时会显示一个关闭按扭,默认否
- */
- closeable?: boolean;
- /**
- * 关闭按扭,如果设置false则不显示
- * @default 'close'
- */
- closeIcon?: string|false;
- /**
- * 关闭按扭大小
- * @default 40
- */
- closeIconSize?: number;
- /**
- * 关闭按扭位置
- */
- closeIconPosition?: PopupCloseButtonPosition,
- /**
- * 指定当前弹出层弹出位置
- */
- position?: PopupPosition,
- /**
- * 遮罩的颜色
- */
- maskColor?: string,
- /**
- * 是否显示遮罩,默认是
- * @default true
- */
- mask?: boolean,
- /**
- * 对话框偏移边距,默认为0,0,0,0
- * @default [0,0,0,0]
- */
- margin?: number[],
- /**
- * 强制设置整体边距(包括遮罩层),默认为[undefined,undefined,undefined,undefined]
- * @default [undefined,undefined,undefined,undefined]
- */
- inset?: (number|string|undefined)[],
- /**
- * 弹出层背景颜色,默认是 白色
- * @default white
- */
- backgroundColor?: string;
- /**
- * 从侧边弹出时,是否自动设置安全区,默认是
- * @default true
- */
- safeArea?: boolean,
- /**
- * 指定当前弹出层的特殊样式
- */
- innerStyle?: ViewStyle,
- /**
- * 指定弹出层动画时长,毫秒
- * @default 230
- */
- duration?: number,
- /**
- * 指定弹出层从侧边弹出的高度,如果是横向弹出,则设置宽度,默认是30%, 设置 auto 让大小自动根据内容调整
- * @default '30%'
- */
- size?: string|number;
- }
- const emit = defineEmits([ 'update:show', 'close', 'closeAnimFinished' ])
- const props = withDefaults(defineProps<PopupProps>(), {
- closeIcon: 'close',
- closeIconSize: 40,
- closeIconPosition: 'right',
- position: 'center',
- maskColor: 'background.mask',
- mask: true,
- margin: () => [0,0,0,0],
- inset: () => {
- const arr : (number|undefined|string)[] = [undefined,undefined,undefined,undefined]
- // #ifdef H5
- arr[0] = '44px';
- // #endif
- return arr;
- },
- backgroundColor: 'white',
- safeArea: true,
- duration: 230,
- size: '30%',
- });
- function handleClick(e: Event) {
- e.stopPropagation();
- }
- function handleClose(e: Event) {
- e.stopPropagation();
- if (props.closeable)
- doClose();
- }
- function doClose() {
- emit('update:show', false);
- emit('close');
- }
- const themeContext = useTheme();
- const show2 = ref(false);
- const showAnimState = ref(false);
- const radius = computed(() => props.round ? themeContext.resolveThemeSize('PopupRadius', 30) : 0);
- const dialogSize = computed(() => themeContext.resolveThemeSize(props.size));
- let lateStopTimer : SimpleDelay|undefined;
- watch(() => props.show, (v) => {
- show2.value = true;
- if (!v) {
- showAnimState.value = false;
- if (lateStopTimer)
- lateStopTimer.stop();
- lateStopTimer = new SimpleDelay(undefined, () => {
- lateStopTimer = undefined;
- show2.value = false;
- }, props.duration)
- lateStopTimer.start();
- } else {
- if (lateStopTimer)
- lateStopTimer.stop();
- lateStopTimer = new SimpleDelay(undefined, () => {
- lateStopTimer = undefined;
- showAnimState.value = true
- }, 20)
- lateStopTimer.start();
- }
- });
- </script>
- <style lang="scss">
- .nana-popup {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 110;
- display: flex;
- flex-direction: column;
- pointer-events: none;
- overflow: hidden;
- &.show2 {
- pointer-events: auto;
- .nana-popup-mask {
- pointer-events: auto;
- }
- }
- &.no-mask {
- .nana-popup-mask {
- pointer-events: none;
- }
- }
- // &.stop {
- // }
- .nana-popup-mask {
- position: absolute;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- z-index: 111;
- pointer-events: none;
- opacity: 0;
- transition: opacity ease-in-out 0.3s;
- }
- .nana-popup-content {
- position: relative;
- z-index: 112;
- overflow: hidden;
- transition: all ease-in-out 0.3s;
- opacity: 0.3;
- pointer-events: auto;
- &.center {
- transform: translateY(-10px);
- }
- &.top {
- transform: translateY(-100vh);
- }
- &.bottom {
- transform: translateY(200vh);
- }
- &.left {
- transform: translateX(-750rpx);
- }
- &.right {
- transform: translateX(750rpx);
- }
- }
- &.show {
- .nana-popup-mask {
- opacity: 1;
- }
- .nana-popup-content {
- opacity: 1;
- transform: translateX(0) translateY(0);
- }
- }
- }
- </style>
|