init
This commit is contained in:
295
uni_modules/nutui-uni/components/popover/popover.vue
Normal file
295
uni_modules/nutui-uni/components/popover/popover.vue
Normal file
@@ -0,0 +1,295 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, defineComponent, getCurrentInstance, nextTick, onMounted, ref, watch } from 'vue'
|
||||
import type { ComponentInternalInstance, CSSProperties } from 'vue'
|
||||
import { CHOOSE_EVENT, CLOSE_EVENT, OPEN_EVENT, PREFIX, UPDATE_VISIBLE_EVENT } from '../_constants'
|
||||
import { useRect } from '../_hooks'
|
||||
import { getMainClass, getMainStyle, getRandomId } from '../_utils'
|
||||
import NutIcon from '../icon/icon.vue'
|
||||
import NutPopup from '../popup/popup.vue'
|
||||
import { popoverEmits, popoverProps } from './popover'
|
||||
import type { PopoverRootPosition } from './type'
|
||||
|
||||
const props = defineProps(popoverProps)
|
||||
const emit = defineEmits(popoverEmits)
|
||||
const instance = getCurrentInstance() as ComponentInternalInstance
|
||||
const popoverID = `popoverRef${getRandomId()}`
|
||||
const popoverContentID = `popoverContentRef${getRandomId()}`
|
||||
const showPopup = ref(props.visible)
|
||||
const rootPosition = ref<PopoverRootPosition>()
|
||||
|
||||
const elRect = ref({
|
||||
width: 0,
|
||||
height: 0,
|
||||
})
|
||||
const classes = computed(() => {
|
||||
return getMainClass(props, componentName, {
|
||||
[`nut-popover--${props.theme}`]: true,
|
||||
})
|
||||
})
|
||||
const popoverArrow = computed(() => {
|
||||
const prefixCls = 'nut-popover-arrow'
|
||||
const loca = props.location
|
||||
const direction = loca.split('-')[0]
|
||||
return `${prefixCls} ${prefixCls}-${direction} ${prefixCls}--${loca}`
|
||||
})
|
||||
const popoverArrowStyle = computed(() => {
|
||||
const styles: CSSProperties = {}
|
||||
const { bgColor, arrowOffset, location } = props
|
||||
const direction = location.split('-')[0]
|
||||
const skew = location.split('-')[1]
|
||||
const base = 16
|
||||
|
||||
if (bgColor)
|
||||
styles[`border${upperCaseFirst(direction)}Color` as any] = bgColor
|
||||
|
||||
if (props.arrowOffset !== 0) {
|
||||
if (['bottom', 'top'].includes(direction)) {
|
||||
if (!skew)
|
||||
styles.left = `calc(50% + ${arrowOffset}px)`
|
||||
|
||||
if (skew === 'start')
|
||||
styles.left = `${base + arrowOffset}px`
|
||||
|
||||
if (skew === 'end')
|
||||
styles.right = `${base - arrowOffset}px`
|
||||
}
|
||||
|
||||
if (['left', 'right'].includes(direction)) {
|
||||
if (!skew)
|
||||
styles.top = `calc(50% - ${arrowOffset}px)`
|
||||
|
||||
if (skew === 'start')
|
||||
styles.top = `${base - arrowOffset}px`
|
||||
|
||||
if (skew === 'end')
|
||||
styles.bottom = `${base + arrowOffset}px`
|
||||
}
|
||||
}
|
||||
return styles
|
||||
})
|
||||
|
||||
function upperCaseFirst(str: string) {
|
||||
str = str.toLowerCase()
|
||||
str = str.replace(/\b\w+\b/g, word => word.substring(0, 1).toUpperCase() + word.substring(1))
|
||||
return str
|
||||
}
|
||||
|
||||
const getRootPosition = computed(() => {
|
||||
const styles: CSSProperties = {}
|
||||
if (!rootPosition.value) {
|
||||
styles.visibility = 'hidden'
|
||||
return styles
|
||||
}
|
||||
|
||||
const contentWidth = elRect.value.width
|
||||
const contentHeight = elRect.value.height
|
||||
const { width, height, left, top, right } = rootPosition.value
|
||||
const { location, offset } = props
|
||||
const direction = location?.split('-')[0]
|
||||
const skew = location?.split('-')[1]
|
||||
let cross = 0
|
||||
let parallel = 0
|
||||
if (Array.isArray(offset) && offset?.length === 2) {
|
||||
cross += Number(offset[1])
|
||||
parallel += Number(offset[0])
|
||||
}
|
||||
if (width) {
|
||||
if (['bottom', 'top'].includes(direction)) {
|
||||
const h = direction === 'bottom' ? height + cross : -(contentHeight + cross)
|
||||
styles.top = `${top + h}px`
|
||||
|
||||
if (!skew)
|
||||
styles.left = `${-(contentWidth - width) / 2 + left + parallel}px`
|
||||
|
||||
if (skew === 'start')
|
||||
styles.left = `${left + parallel}px`
|
||||
|
||||
if (skew === 'end')
|
||||
styles.left = `${right + parallel}px`
|
||||
}
|
||||
if (['left', 'right'].includes(direction)) {
|
||||
const contentW = direction === 'left' ? -(contentWidth + cross) : width + cross
|
||||
styles.left = `${left + contentW}px`
|
||||
if (!skew)
|
||||
styles.top = `${top - contentHeight / 2 + height / 2 - 4 + parallel}px`
|
||||
|
||||
if (skew === 'start')
|
||||
styles.top = `${top + parallel}px`
|
||||
|
||||
if (skew === 'end')
|
||||
styles.top = `${top + height + parallel}px`
|
||||
}
|
||||
}
|
||||
|
||||
if (elRect.value.width === 0)
|
||||
styles.visibility = 'hidden'
|
||||
else
|
||||
styles.visibility = 'initial'
|
||||
|
||||
return styles
|
||||
})
|
||||
|
||||
const styles = computed(() => {
|
||||
const styles: CSSProperties = {}
|
||||
if (props.bgColor)
|
||||
styles.background = props.bgColor
|
||||
|
||||
return getMainStyle(props, styles)
|
||||
})
|
||||
|
||||
// 获取宽度
|
||||
async function getContentWidth() {
|
||||
uni.createSelectorQuery().selectViewport().scrollOffset((res: any) => {
|
||||
const distance = res.scrollTop
|
||||
|
||||
if (props.targetId) {
|
||||
useRect(props.targetId, instance.root).then((rect) => {
|
||||
rootPosition.value = {
|
||||
width: rect.width!,
|
||||
height: rect.height!,
|
||||
left: rect.left!,
|
||||
top: rect.top! + distance!,
|
||||
right: rect.right!,
|
||||
}
|
||||
})
|
||||
}
|
||||
else {
|
||||
useRect(popoverID, instance).then((rect) => {
|
||||
rootPosition.value = {
|
||||
width: rect.width!,
|
||||
height: rect.height!,
|
||||
left: rect.left!,
|
||||
top: rect.top! + distance!,
|
||||
right: rect.right!,
|
||||
}
|
||||
})
|
||||
}
|
||||
}).exec()
|
||||
|
||||
setTimeout(() => {
|
||||
getPopoverContentW()
|
||||
}, 300)
|
||||
}
|
||||
|
||||
async function getPopoverContentW() {
|
||||
useRect(popoverContentID, instance).then((rect) => {
|
||||
elRect.value = {
|
||||
width: rect.width!,
|
||||
height: rect.height!,
|
||||
}
|
||||
})
|
||||
}
|
||||
watch(
|
||||
() => props.visible,
|
||||
(value) => {
|
||||
showPopup.value = value
|
||||
if (value) {
|
||||
nextTick(() => {
|
||||
getContentWidth()
|
||||
})
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.location,
|
||||
(value) => {
|
||||
getContentWidth()
|
||||
},
|
||||
)
|
||||
function update(val: boolean) {
|
||||
emit('update', val)
|
||||
emit(UPDATE_VISIBLE_EVENT, val)
|
||||
}
|
||||
function openPopover() {
|
||||
update(!props.visible)
|
||||
emit(OPEN_EVENT)
|
||||
}
|
||||
function closePopover() {
|
||||
emit(UPDATE_VISIBLE_EVENT, false)
|
||||
emit(CLOSE_EVENT)
|
||||
}
|
||||
function chooseItem(item: any, index: number) {
|
||||
!item.disabled && emit(CHOOSE_EVENT, item, index)
|
||||
}
|
||||
function clickContent() {
|
||||
if (props.closeOnClickAction)
|
||||
closePopover()
|
||||
}
|
||||
function clickAway() {
|
||||
props.closeOnClickOutside && closePopover()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
setTimeout(() => {
|
||||
getContentWidth()
|
||||
}, 300)
|
||||
})
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
const componentName = `${PREFIX}-popover`
|
||||
|
||||
export default defineComponent({
|
||||
name: componentName,
|
||||
options: {
|
||||
virtualHost: true,
|
||||
addGlobalClass: true,
|
||||
styleIsolation: 'shared',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view
|
||||
v-if="!targetId"
|
||||
:id="popoverID"
|
||||
class="nut-popover-wrapper"
|
||||
@click="openPopover"
|
||||
>
|
||||
<slot name="reference" />
|
||||
</view>
|
||||
<view :class="classes" :style="getRootPosition">
|
||||
<NutPopup
|
||||
v-model:visible="showPopup"
|
||||
:destroy-on-close="false"
|
||||
:pop-class="`nut-popover-content nut-popover-content--${location}`"
|
||||
:custom-style="styles"
|
||||
:position="`` as any"
|
||||
:transition="`nut-popover` as any"
|
||||
:overlay="overlay"
|
||||
:duration="+duration"
|
||||
:overlay-style="overlayStyle"
|
||||
:overlay-class="overlayClass"
|
||||
:close-on-click-overlay="closeOnClickOverlay"
|
||||
>
|
||||
<view :id="popoverContentID" class="nut-popover-content-group" @click.stop="clickContent">
|
||||
<view v-if="showArrow" :class="popoverArrow" :style="popoverArrowStyle" />
|
||||
<slot name="content" />
|
||||
<view
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
class="nut-popover-menu-item"
|
||||
:class="[item.className, item.disabled && 'nut-popover-menu-disabled']"
|
||||
@click="chooseItem(item, index)"
|
||||
>
|
||||
<NutIcon v-if="item.icon" :name="item.icon" custom-class="nut-popover-item-img" />
|
||||
<view class="nut-popover-menu-item-name">
|
||||
{{ item.name }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</NutPopup>
|
||||
|
||||
<view
|
||||
class="nut-popover-content-bg"
|
||||
:class="{ 'nut-hidden': !showPopup }"
|
||||
@touchmove="clickAway"
|
||||
@click="clickAway"
|
||||
/>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style lang="scss">
|
||||
@import './index';
|
||||
</style>
|
||||
Reference in New Issue
Block a user