UNPKG

magiccube-vue3

Version:

vue3-js版组件库

210 lines (185 loc) 7.84 kB
import { ref, Teleport, onUnmounted, nextTick, getCurrentInstance } from 'vue' import * as utils from './common' export default { props: { picker: [Function, Object, null], autoClose: { type: Boolean, default: true } }, emits: ['close'], setup(props, { emit, slots, expose }) { /** * 每个dropdown都有一个的id */ const uuid = utils._uuid() /* 动态锚点 */ const instance = getCurrentInstance() const globalOptions = instance.appContext?.config?.globalProperties?.$ELEMENT const TELEPORT_NAME = globalOptions.teleportName? `.${globalOptions.teleportName}` : 'body' const itemFontSize = globalOptions.dropdownFontSize? globalOptions.dropdownFontSize + 'px' : '' /** * 父级可滚动元素集 */ let scrollEventNode = null const dropdownEl = ref(null) const top = ref(0) const left = ref(0) const width = ref(0) const reverse = ref(false) const slide = ref(false) const arrowLeft = ref(0) const isFixedWidth = ref(false) /** * dropdown打开时存储top、left、父级的scrollTop、父级的scrollLeft初始值 * 在页面滚动时进行监听 dropdown会跟随改变位置 */ let originDropdownSite = {} const addScrollListener = () => { scrollEventNode = utils.getParentScrollElement(props.picker) if(scrollEventNode && scrollEventNode.length){ scrollEventNode.forEach(item => { if ((item.scrollTop || item.scrollTop === 0) && !originDropdownSite.scrollTop && originDropdownSite.scrollTop !== 0) originDropdownSite.scrollTop = item.scrollTop if ((item.scrollLeft || item.scrollLeft === 0) && !originDropdownSite.scrollLeft && originDropdownSite.scrollLeft !== 0) originDropdownSite.scrollLeft = item.scrollLeft item.addEventListener('scroll', handleScrollListener) }) } else { window.addEventListener('scroll', handleWindowScrollListener) } } /* 有滚动元素包裹 */ const removeScrollListener = () => { if (!scrollEventNode) return if(scrollEventNode.length){ scrollEventNode.forEach(item => item.removeEventListener('scroll', handleScrollListener)) } else { window.removeEventListener('scroll', handleWindowScrollListener) } scrollEventNode = null } /* 滚动设置在body级别(页面自带滚动属性) */ const handleScrollListener = (e) => { const y = e.target.scrollTop - originDropdownSite.scrollTop const x = e.target.scrollLeft - originDropdownSite.scrollLeft top.value = originDropdownSite.top - y + 'px' left.value = originDropdownSite.left - x + 'px' } const handleWindowScrollListener = (e) => { const target = e.target.scrollingElement const y = target.scrollTop const x = target.scrollLeft top.value = originDropdownSite.top - y + 10 + 'px' left.value = originDropdownSite.left - x + 'px' } /** * 点击dropdown以外地方自动收起 */ const handleClickListener = (e) => { try{ const item = props.picker const _dropdownEl = document.getElementById(uuid) if(!_dropdownEl) return if (item && !item.contains?.(e.target) && !_dropdownEl?.contains?.(e.target)) emit('close') } catch(err) { throw new Error(err.message) } } /** * 展开 slide-down */ const visible = (options) => { slide.value = true const { event, pickerHeight, pickerWidth, dropdownWidth, dropdownHeight, fixedWidth } = options const clientHeight = event.clientY const pageWidth = document.body.clientWidth const pageHeight = document.body.clientHeight const pickerTop = event.clientY - event.offsetY const pickerLeft = event.clientX - event.offsetX let _y = 0 let _x = 0 width.value = dropdownWidth || pickerWidth || dropdownEl.value.offsetWidth const _w = fixedWidth? (width.value) : Math.max(dropdownWidth, pickerWidth, dropdownEl.value.offsetWidth) // 兼容日历的宽度限制 isFixedWidth.value = fixedWidth /** 计算是否超出屏幕 向下或向上打开 */ if ((clientHeight + dropdownHeight) > pageHeight) { _y = pickerTop - dropdownHeight - 10 // 如果反向打开超出屏幕 不进行反转 并顶部与屏幕对齐 if(_y < 0) { _y = 0 } else { reverse.value = true } } else { _y = pickerTop + pickerHeight + 8 reverse.value = false } /** 计算是否超出屏幕 靠左或靠右 */ if(_w + pickerLeft > pageWidth){ _x = pickerLeft + (pageWidth - (_w + pickerLeft + 10)) } else { _x = pickerLeft } arrowLeft.value = pickerLeft - _x + 16 top.value = _y + 'px' left.value = _x + 'px' originDropdownSite.top = _y originDropdownSite.left = _x addScrollListener() } /** * 收起 slide-up */ const invisible = () => { originDropdownSite = {} slide.value = false removeScrollListener() } const mouseleave = () => props.autoClose && emit('close', 'mouse-leave-dropdown') nextTick(() => { window.addEventListener('click', handleClickListener, true) }) onUnmounted(() => { window.removeEventListener('click', handleClickListener, true) /** * 注销并移除下拉dom */ removeScrollListener() const target = document.getElementById(uuid) target && target.parentNode.removeChild(target) }) expose({ visible, invisible }) return () => ( <Teleport to={TELEPORT_NAME}> <div ref={dropdownEl} id={uuid} class={[ 'mc-dropdown-panel', { 'slide-down': slide.value, 'slide-up': !slide.value, 'reverse': reverse.value, } ]} onMouseleave={mouseleave} style={{ width: isFixedWidth.value? width.value + 'px' : 'auto', minWidth: width.value? width.value + 'px' : '', top: top.value, left: left.value, // fontSize: itemFontSize //下拉控件影响到城市等控件 暂时不进行字号统一处理 }}> {slots.default ? slots.default() : ''} {/* 本项目目前UI规范中没有这个箭头,但是不要删除,以免以后会用到 <span class="mc-dropdown-panel__arrow" style={{ left: arrowLeft.value + 'px' }}></span> */} </div> </Teleport> ) } }