l-popup.uvue 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. <template>
  2. <l-overlay
  3. :visible="innerValue"
  4. :zIndex="overlayZIndex"
  5. :appear="true"
  6. :preventScrollThrough="preventScrollThrough"
  7. :l-style="overlayStyle"
  8. @click="handleOverlayClick"
  9. v-if="destroyOnClose ? display && overlay : overlay">
  10. </l-overlay>
  11. <view class="l-popup"
  12. ref="popupRef"
  13. v-if="destroyOnClose ? display : inited"
  14. :class="rootClass"
  15. :style="[styles, lStyle]"
  16. @transitionend="finished">
  17. <slot></slot>
  18. <view class="l-popup__close" v-if="closeable" @click="handleClose">
  19. <slot name="close-btn">
  20. <l-icon class="l-popup__close-icon" :name="closeIcon" size="27px" :color="iconColor" />
  21. </slot>
  22. </view>
  23. </view>
  24. </template>
  25. <script lang="uts" setup>
  26. /**
  27. * Popup 弹出层组件
  28. * @description 提供多种位置的弹窗展示能力,支持自定义内容和动画效果
  29. * <br>插件类型:LPopupComponentPublicInstance
  30. * @tutorial https://ext.dcloud.net.cn/plugin?name=lime-popup
  31. *
  32. * @property {boolean} closeable 显示关闭按钮(默认:false)
  33. * @property {boolean} closeOnClickOverlay 点击遮罩关闭(默认:true)
  34. * @property {boolean} destroyOnClose 关闭时销毁内容(默认:false)
  35. * @property {string} overlayStyle 遮罩层样式(支持CSS字符串)
  36. * @property {'top' | 'left' | 'right' | 'bottom' | 'center' | ''} position 弹出位置
  37. * @value top 从顶部滑入
  38. * @value bottom 从底部滑入
  39. * @value left 从左侧滑入
  40. * @value right 从右侧滑入
  41. * @value center 居中显示(默认)
  42. * @property {boolean} preventScrollThrough 阻止滚动穿透(默认:true)
  43. * @property {boolean} overlay 显示遮罩层(默认:true)
  44. * @property {string} transitionName 自定义动画名称(配合transition使用)
  45. * @property {string|number|Array} radius 圆角 可以是字符,数值,数组
  46. * @property {boolean} visible 控制显示/隐藏(支持v-model)
  47. * @property {number} zIndex 组件层级(默认:999)
  48. * @property {number} duration 动画时长(单位ms,默认:300)
  49. * @property {string} bgColor 内容区域背景色(默认:#ffffff)
  50. * @property {string} closeIcon 关闭图标名称/路径(默认:'close')
  51. * @property {string} iconColor 关闭图标颜色(默认:#333)
  52. * @property {string} lStyle 自定义内容区样式(支持CSS字符串)
  53. * @property {boolean} safeAreaInsetBottom 适配底部安全区域(默认:true)
  54. * @property {boolean} safeAreaInsetTop 适配顶部安全区域(默认:true)
  55. * @event {Function} close 关闭时触发(返回触发来源:'close-btn' | 'overlay')
  56. * @event {Function} change 切换时触发
  57. * @event {Function} click-overlay 点击遮罩触发
  58. * @event {Function} click-close 点击关闭触发
  59. * @event {Function} open 打开触发
  60. * @event {Function} opened 打开完成触发
  61. * @event {Function} closed 关闭完成触发
  62. * @event {Function} before-enter
  63. * @event {Function} enter
  64. * @event {Function} after-enter
  65. * @event {Function} before-leave
  66. * @event {Function} leave
  67. * @event {Function} after-leave
  68. */
  69. import { useTransition, type UseTransitionOptions, type TransitionEmitStatus } from '@/uni_modules/lime-transition';
  70. import { convertRadius } from './utils'
  71. import { PopupProps } from './type';
  72. const emit = defineEmits(['change', 'click-overlay', 'click-close', 'open', 'opened','close','closed', 'before-enter', 'enter', 'after-enter', 'before-leave', 'leave', 'after-leave'])
  73. const props = withDefaults(defineProps<PopupProps>(),{
  74. closeable: false,
  75. overlay: true,
  76. closeOnClickOverlay: true,
  77. preventScrollThrough: true,
  78. destroyOnClose: false,
  79. safeAreaInsetBottom: true,
  80. safeAreaInsetTop: false,
  81. position: 'center',
  82. zIndex: 999,
  83. duration: 300,
  84. closeIcon: 'close'
  85. })
  86. const modelValue = defineModel({type: Boolean});
  87. const innerValue = computed({
  88. set(value: boolean) {
  89. modelValue.value = value;
  90. emit('change', value)
  91. },
  92. get():boolean {
  93. // #ifdef APP-ANDROID
  94. return (props.visible ?? false) || modelValue.value// ?? false
  95. // #endif
  96. // #ifndef APP-ANDROID
  97. return props.visible || modelValue.value
  98. // #endif
  99. }
  100. } as WritableComputedOptions<boolean>)
  101. // const hosKey = ref(0)
  102. const status = ref<TransitionEmitStatus>('before-enter')
  103. const {inited, display, classes, finished} = useTransition({
  104. defaultName: props.transitionName ?? 'popup-fade',
  105. appear: innerValue.value,
  106. emits: (name:TransitionEmitStatus) => {
  107. status.value = name
  108. if(name == 'before-enter') {
  109. emit('open')
  110. } else if(name == 'after-enter') {
  111. emit('opened')
  112. // setTimeout(()=>{
  113. // if(hosKey.value > 0) return
  114. // hosKey.value++
  115. // },5)
  116. } else if(name == 'before-leave') {
  117. emit('close')
  118. } else if(name == 'after-leave') {
  119. emit('closed')
  120. }
  121. emit(name)
  122. },
  123. visible: (): boolean => innerValue.value,
  124. duration: props.duration,
  125. } as UseTransitionOptions)
  126. const overlayZIndex = computed(():number => props.zIndex > 0 ? props.zIndex - 1: 998);
  127. const rootClass = computed(():string=>{
  128. const safe = props.safeAreaInsetTop && props.position == 'top'
  129. ? 'l-popup--safe-top'
  130. : props.safeAreaInsetBottom && props.position == 'bottom'
  131. ? 'l-popup--safe-bottom'
  132. : ''
  133. return `l-popup--${props.position} ${safe} ${classes.value}`
  134. })
  135. const {safeAreaInsets} = uni.getWindowInfo()
  136. const styles = computed<Map<string,any>>(():Map<string,any> => {
  137. const style = new Map<string,any>();
  138. // #ifdef UNI-APP-X && APP
  139. style.set('transition-duration', (['after-leave', 'before-enter'].includes(status.value) ? 0 : props.duration) + 'ms')
  140. // #endif
  141. // #ifndef UNI-APP-X && APP
  142. style.set('transition-duration', props.duration + 'ms')
  143. // #endif
  144. if (props.bgColor != null) {
  145. style.set("background", props.bgColor!)
  146. }
  147. if (props.zIndex > 0) {
  148. style.set("z-index", props.zIndex)
  149. }
  150. // if(props.safeAreaInsetBottom && props.position == 'bottom') {
  151. // style.set('padding-bottom', safeAreaInsets.bottom + 'px')
  152. // }
  153. // if(props.safeAreaInsetTop && props.position == 'top') {
  154. // style.set('padding-top', safeAreaInsets.top + 'px')
  155. // }
  156. if(props.radius != null) {
  157. const values = convertRadius(props.radius!)
  158. style.set('border-top-left-radius', values[0])
  159. style.set('border-top-right-radius', values[1])
  160. style.set('border-bottom-right-radius', values[2])
  161. style.set('border-bottom-left-radius', values[3])
  162. }
  163. // #ifndef APP
  164. if (!display.value) {
  165. // 会导致picker触发pick
  166. // style.set("display", "none")
  167. style.set("pointer-events", "none")
  168. }
  169. // #endif
  170. return style
  171. })
  172. const handleOverlayClick = () => {
  173. if(props.closeOnClickOverlay) {
  174. innerValue.value = false;
  175. emit('click-overlay')
  176. }
  177. }
  178. const handleClose = () => {
  179. innerValue.value = false;
  180. emit('click-close')
  181. }
  182. // #ifdef APP
  183. const popupRef = ref<UniElement|null>(null)
  184. watchEffect(()=>{
  185. // 会导致picker触发pick
  186. if(!display.value) {
  187. // popupRef.value?.style.setProperty('display', 'none')
  188. popupRef.value?.style.setProperty('pointer-events', 'none')
  189. popupRef.value?.style.setProperty('z-index', -10000)
  190. } else {
  191. // popupRef.value?.style.setProperty('display', 'flex')
  192. popupRef.value?.style.setProperty('pointer-events', 'auto')
  193. popupRef.value?.style.setProperty('z-index', props.zIndex)
  194. }
  195. })
  196. // #endif
  197. </script>
  198. <style lang="scss">
  199. @import "./index-u";
  200. </style>