|
@@ -1,214 +1,160 @@
|
|
|
<template>
|
|
<template>
|
|
|
- <!-- 气泡弹出层 按钮 -->
|
|
|
|
|
- <view style="display: inline-block" :class="customClass" @click.stop="openPopover" ref="reference">
|
|
|
|
|
- <slot name="reference"></slot
|
|
|
|
|
- ></view>
|
|
|
|
|
-
|
|
|
|
|
- <nut-popup
|
|
|
|
|
- ref="popoverRef"
|
|
|
|
|
- :pop-class="classes"
|
|
|
|
|
- v-model:visible="showPopup"
|
|
|
|
|
- :overlay="false"
|
|
|
|
|
- @clickOverlay="clickOverlay"
|
|
|
|
|
- >
|
|
|
|
|
- <!-- 气泡弹出层 箭头 -->
|
|
|
|
|
- <view :class="popoverArrow" v-if="showArrow"> </view>
|
|
|
|
|
- <!-- 气泡弹出层 内容 -->
|
|
|
|
|
- <slot name="content"></slot>
|
|
|
|
|
- <view class="popover-menu" :class="popoverContent" ref="popoverMenu">
|
|
|
|
|
- <view
|
|
|
|
|
- v-for="(item, index) in list"
|
|
|
|
|
- :key="index"
|
|
|
|
|
- :class="[item.className, { 'popover-menu-item': true, disabled: item.disabled }]"
|
|
|
|
|
- @click.stop="chooseItem(item, index)"
|
|
|
|
|
- >
|
|
|
|
|
- <slot v-if="item.icon"> <nut-icon v-bind="$attrs" class="item-img" :name="item.icon"></nut-icon></slot>
|
|
|
|
|
- <view class="popover-menu-name">{{ item.name }}</view>
|
|
|
|
|
|
|
+ <view :class="['nut-popover', `nut-popover--${theme}`, `${customClass}`]">
|
|
|
|
|
+ <view class="nut-popover-wrapper" @click="openPopover" ref="popoverRef"><slot name="reference"></slot></view>
|
|
|
|
|
+
|
|
|
|
|
+ <nut-popup
|
|
|
|
|
+ :popClass="`nut-popover-content nut-popover-content--${location}`"
|
|
|
|
|
+ :style="getStyles"
|
|
|
|
|
+ v-model:visible="showPopup"
|
|
|
|
|
+ position=""
|
|
|
|
|
+ transition="nut-popover"
|
|
|
|
|
+ :overlay="overlay"
|
|
|
|
|
+ :duration="duration"
|
|
|
|
|
+ :overlayStyle="overlayStyle"
|
|
|
|
|
+ :overlayClass="overlayClass"
|
|
|
|
|
+ :closeOnClickOverlay="closeOnClickOverlay"
|
|
|
|
|
+ >
|
|
|
|
|
+ <view ref="popoverContentRef" class="nut-popover-content-group">
|
|
|
|
|
+ <view :class="popoverArrow" v-if="showArrow"> </view>
|
|
|
|
|
+ <slot name="content"></slot>
|
|
|
|
|
+ <view
|
|
|
|
|
+ v-for="(item, index) in list"
|
|
|
|
|
+ :key="index"
|
|
|
|
|
+ :class="[item.className, item.disabled && 'nut-popover-menu-disabled', 'nut-popover-menu-item']"
|
|
|
|
|
+ @click.stop="chooseItem(item, index)"
|
|
|
|
|
+ >
|
|
|
|
|
+ <slot v-if="item.icon">
|
|
|
|
|
+ <nut-icon v-bind="$attrs" class="item-img" :classPrefix="iconPrefix" :name="item.icon"></nut-icon
|
|
|
|
|
+ ></slot>
|
|
|
|
|
+ <view class="nut-popover-menu-name">{{ item.name }}</view>
|
|
|
|
|
+ </view>
|
|
|
</view>
|
|
</view>
|
|
|
- </view>
|
|
|
|
|
- </nut-popup>
|
|
|
|
|
|
|
+ </nut-popup>
|
|
|
|
|
+ </view>
|
|
|
</template>
|
|
</template>
|
|
|
<script lang="ts">
|
|
<script lang="ts">
|
|
|
-import { onMounted, computed, watch, ref, PropType, toRefs, nextTick, onUnmounted } from 'vue';
|
|
|
|
|
|
|
+import { computed, watch, ref, PropType, CSSProperties, reactive } from 'vue';
|
|
|
import { createComponent } from '@/packages/utils/create';
|
|
import { createComponent } from '@/packages/utils/create';
|
|
|
-const { componentName, create } = createComponent('popover');
|
|
|
|
|
-import Popup from '../popup/index.vue';
|
|
|
|
|
-import { popupProps } from '../popup/props';
|
|
|
|
|
-import Button from '../button/index.vue';
|
|
|
|
|
-import { createPopper } from '@popperjs/core/lib/popper-lite';
|
|
|
|
|
-import offsetModifier from '@popperjs/core/lib/modifiers/offset';
|
|
|
|
|
-import type { Instance, Placement } from '@popperjs/core';
|
|
|
|
|
-
|
|
|
|
|
|
|
+const { create } = createComponent('popover');
|
|
|
export default create({
|
|
export default create({
|
|
|
- inheritAttrs: false,
|
|
|
|
|
- components: {
|
|
|
|
|
- [Popup.name]: Popup,
|
|
|
|
|
- [Button.name]: Button
|
|
|
|
|
- },
|
|
|
|
|
|
|
+ components: {},
|
|
|
props: {
|
|
props: {
|
|
|
- ...popupProps,
|
|
|
|
|
- list: {
|
|
|
|
|
- type: Array,
|
|
|
|
|
- default: []
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- theme: {
|
|
|
|
|
- type: String as PropType<import('./type').PopoverTheme>,
|
|
|
|
|
- default: 'light'
|
|
|
|
|
- },
|
|
|
|
|
-
|
|
|
|
|
- location: {
|
|
|
|
|
- type: String as PropType<import('./type').PopoverLocation>,
|
|
|
|
|
- default: 'bottom'
|
|
|
|
|
- },
|
|
|
|
|
- // 位置出现的偏移量
|
|
|
|
|
- offset: {
|
|
|
|
|
- type: Array,
|
|
|
|
|
- default: [0, 12]
|
|
|
|
|
- },
|
|
|
|
|
- customClass: {
|
|
|
|
|
- type: String,
|
|
|
|
|
- default: ''
|
|
|
|
|
- },
|
|
|
|
|
- showArrow: {
|
|
|
|
|
- type: Boolean,
|
|
|
|
|
- default: true
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ visible: { type: Boolean, default: false },
|
|
|
|
|
+ list: { type: Array, default: [] },
|
|
|
|
|
+ theme: { type: String as PropType<import('./type').PopoverTheme>, default: 'light' },
|
|
|
|
|
+ location: { type: String as PropType<import('./type').PopoverLocation>, default: 'bottom' },
|
|
|
|
|
+ offset: { type: Array, default: [0, 12] },
|
|
|
|
|
+ customClass: { type: String, default: '' },
|
|
|
|
|
+ showArrow: { type: Boolean, default: true },
|
|
|
|
|
+ iconPrefix: { type: String, default: 'nut-icon' },
|
|
|
|
|
+ duration: { type: [Number, String], default: 0.3 },
|
|
|
|
|
+ overlay: { type: Boolean, default: false },
|
|
|
|
|
+ overlayClass: { type: String, default: '' },
|
|
|
|
|
+ overlayStyle: { type: Object as PropType<CSSProperties> },
|
|
|
|
|
+ closeOnClickOverlay: { type: Boolean, default: true },
|
|
|
|
|
+ closeOnClickAction: { type: Boolean, default: true },
|
|
|
|
|
+ closeOnClickOutside: { type: Boolean, default: true }
|
|
|
},
|
|
},
|
|
|
emits: ['update', 'update:visible', 'close', 'choose', 'open'],
|
|
emits: ['update', 'update:visible', 'close', 'choose', 'open'],
|
|
|
setup(props, { emit }) {
|
|
setup(props, { emit }) {
|
|
|
- let popper: Instance | null;
|
|
|
|
|
- const reference = ref();
|
|
|
|
|
const popoverRef = ref();
|
|
const popoverRef = ref();
|
|
|
-
|
|
|
|
|
|
|
+ const popoverContentRef = ref();
|
|
|
const showPopup = ref(props.visible);
|
|
const showPopup = ref(props.visible);
|
|
|
-
|
|
|
|
|
- const { theme, location } = toRefs(props);
|
|
|
|
|
-
|
|
|
|
|
- const classes = computed(() => {
|
|
|
|
|
- const prefixCls = componentName;
|
|
|
|
|
-
|
|
|
|
|
- return `${prefixCls} ${prefixCls}--${theme.value}`;
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- const popoverContent = computed(() => {
|
|
|
|
|
- const prefixCls = 'popover-content';
|
|
|
|
|
- return {
|
|
|
|
|
- [prefixCls]: true,
|
|
|
|
|
- [`${prefixCls}--${location.value}`]: location.value
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ const state = reactive({
|
|
|
|
|
+ rootWidth: 0,
|
|
|
|
|
+ rootHeight: 0
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
const popoverArrow = computed(() => {
|
|
const popoverArrow = computed(() => {
|
|
|
- const prefixCls = 'popover-arrow';
|
|
|
|
|
- return {
|
|
|
|
|
- [prefixCls]: true,
|
|
|
|
|
- [`${prefixCls}--${location.value}`]: location.value
|
|
|
|
|
- };
|
|
|
|
|
|
|
+ const prefixCls = 'nut-popover-arrow';
|
|
|
|
|
+ const loca = props.location;
|
|
|
|
|
+ const direction = loca.split('-')[0];
|
|
|
|
|
+ return `${prefixCls} ${prefixCls}-${direction} ${prefixCls}--${loca}`;
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
- const createPopperInstance = () => {
|
|
|
|
|
- if (reference.value && popoverRef.value) {
|
|
|
|
|
- return createPopper(reference.value, popoverRef.value.popupRef, {
|
|
|
|
|
- placement: props.location,
|
|
|
|
|
- modifiers: [
|
|
|
|
|
- {
|
|
|
|
|
- name: 'computeStyles',
|
|
|
|
|
- options: {
|
|
|
|
|
- adaptive: false,
|
|
|
|
|
- gpuAcceleration: false
|
|
|
|
|
- }
|
|
|
|
|
- },
|
|
|
|
|
- Object.assign({}, offsetModifier, {
|
|
|
|
|
- options: {
|
|
|
|
|
- offset: props.offset
|
|
|
|
|
- }
|
|
|
|
|
- })
|
|
|
|
|
- ]
|
|
|
|
|
- });
|
|
|
|
|
|
|
+ const getStyles = computed(() => {
|
|
|
|
|
+ let cross = +state.rootHeight;
|
|
|
|
|
+ let lengthways = +state.rootWidth;
|
|
|
|
|
+ let { offset, location } = props;
|
|
|
|
|
+ if (Array.isArray(offset) && offset.length == 2) {
|
|
|
|
|
+ cross += +offset[1];
|
|
|
|
|
+ lengthways += +offset[1];
|
|
|
}
|
|
}
|
|
|
- return null;
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const clickOverlay = () => {
|
|
|
|
|
- // console.log('关闭');
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const uploadLocation = () => {
|
|
|
|
|
- nextTick(() => {
|
|
|
|
|
- if (!showPopup.value) return;
|
|
|
|
|
- if (!popper) {
|
|
|
|
|
- popper = createPopperInstance();
|
|
|
|
|
- } else {
|
|
|
|
|
- popper.setOptions({
|
|
|
|
|
- placement: props.location
|
|
|
|
|
- });
|
|
|
|
|
- }
|
|
|
|
|
- });
|
|
|
|
|
- };
|
|
|
|
|
-
|
|
|
|
|
- const clickAway = (event: Event) => {
|
|
|
|
|
- const element = reference.value;
|
|
|
|
|
- if (element && !element.contains(event.target)) {
|
|
|
|
|
- closePopover();
|
|
|
|
|
|
|
+ const direction = location.split('-')[0];
|
|
|
|
|
+ const style: CSSProperties = {};
|
|
|
|
|
+ const mapd: any = {
|
|
|
|
|
+ top: 'bottom',
|
|
|
|
|
+ bottom: 'top',
|
|
|
|
|
+ left: 'right',
|
|
|
|
|
+ right: 'left'
|
|
|
|
|
+ };
|
|
|
|
|
+ if (['top', 'bottom'].includes(direction)) {
|
|
|
|
|
+ style[mapd[direction]] = `${cross}px`;
|
|
|
|
|
+ style.marginLeft = `${offset[0]}px`;
|
|
|
|
|
+ } else {
|
|
|
|
|
+ style[mapd[direction]] = `${lengthways}px`;
|
|
|
|
|
+ style.marginTop = `${offset[0]}px`;
|
|
|
}
|
|
}
|
|
|
- };
|
|
|
|
|
- onMounted(() => {
|
|
|
|
|
- window.addEventListener('click', clickAway, true);
|
|
|
|
|
- });
|
|
|
|
|
-
|
|
|
|
|
- onUnmounted(() => {
|
|
|
|
|
- window.removeEventListener('click', clickAway, true);
|
|
|
|
|
|
|
+ return style;
|
|
|
});
|
|
});
|
|
|
-
|
|
|
|
|
|
|
+ // 获取宽度
|
|
|
|
|
+ const getContentWidth = () => {
|
|
|
|
|
+ const { offsetHeight, offsetWidth } = popoverRef.value;
|
|
|
|
|
+ state.rootHeight = offsetHeight;
|
|
|
|
|
+ state.rootWidth = offsetWidth;
|
|
|
|
|
+ };
|
|
|
watch(
|
|
watch(
|
|
|
() => props.visible,
|
|
() => props.visible,
|
|
|
(value) => {
|
|
(value) => {
|
|
|
showPopup.value = value;
|
|
showPopup.value = value;
|
|
|
- uploadLocation();
|
|
|
|
|
- }
|
|
|
|
|
- );
|
|
|
|
|
-
|
|
|
|
|
- watch(
|
|
|
|
|
- () => props.location,
|
|
|
|
|
- (value) => {
|
|
|
|
|
- uploadLocation();
|
|
|
|
|
|
|
+ if (value) {
|
|
|
|
|
+ window.addEventListener('touchstart', clickAway, true);
|
|
|
|
|
+ getContentWidth();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ window.removeEventListener('touchstart', clickAway, true);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
-
|
|
|
|
|
const update = (val: boolean) => {
|
|
const update = (val: boolean) => {
|
|
|
emit('update', val);
|
|
emit('update', val);
|
|
|
emit('update:visible', val);
|
|
emit('update:visible', val);
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
const openPopover = () => {
|
|
const openPopover = () => {
|
|
|
update(!props.visible);
|
|
update(!props.visible);
|
|
|
emit('open');
|
|
emit('open');
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
const closePopover = () => {
|
|
const closePopover = () => {
|
|
|
- emit('close');
|
|
|
|
|
emit('update:visible', false);
|
|
emit('update:visible', false);
|
|
|
|
|
+ emit('close');
|
|
|
};
|
|
};
|
|
|
-
|
|
|
|
|
const chooseItem = (item: any, index: number) => {
|
|
const chooseItem = (item: any, index: number) => {
|
|
|
- if (item.disabled) {
|
|
|
|
|
- return;
|
|
|
|
|
- }
|
|
|
|
|
emit('choose', item, index);
|
|
emit('choose', item, index);
|
|
|
|
|
+ if (props.closeOnClickAction) {
|
|
|
|
|
+ closePopover();
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+ const clickAway = (event: Event) => {
|
|
|
|
|
+ const element = popoverRef.value;
|
|
|
|
|
+ const elContent = popoverContentRef.value;
|
|
|
|
|
+ if (
|
|
|
|
|
+ element &&
|
|
|
|
|
+ !element.contains(event.target) &&
|
|
|
|
|
+ elContent &&
|
|
|
|
|
+ !elContent.contains(event.target) &&
|
|
|
|
|
+ props.closeOnClickOutside
|
|
|
|
|
+ ) {
|
|
|
|
|
+ closePopover();
|
|
|
|
|
+ }
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
return {
|
|
return {
|
|
|
- classes,
|
|
|
|
|
showPopup,
|
|
showPopup,
|
|
|
openPopover,
|
|
openPopover,
|
|
|
- popoverContent,
|
|
|
|
|
popoverArrow,
|
|
popoverArrow,
|
|
|
closePopover,
|
|
closePopover,
|
|
|
chooseItem,
|
|
chooseItem,
|
|
|
- reference,
|
|
|
|
|
popoverRef,
|
|
popoverRef,
|
|
|
- clickOverlay
|
|
|
|
|
|
|
+ getStyles,
|
|
|
|
|
+ popoverContentRef
|
|
|
};
|
|
};
|
|
|
}
|
|
}
|
|
|
});
|
|
});
|