index.vue 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. <template>
  2. <Teleport :to="teleport" v-if="isWrapTeleport">
  3. <nut-overlay
  4. v-if="overlay"
  5. :visible="visible"
  6. :close-on-click-overlay="closeOnClickOverlay"
  7. :class="overlayClass"
  8. :style="overlayStyle"
  9. :z-index="zIndex"
  10. :lock-scroll="lockScroll"
  11. :duration="duration"
  12. @click="onClickOverlay"
  13. />
  14. <Transition :name="transitionName" @after-enter="onOpened" @after-leave="onClosed">
  15. <view v-show="visible" :class="classes" :style="popStyle" @click="onClick" ref="popupRef">
  16. <slot v-if="showSlot"></slot>
  17. <view
  18. v-if="closed"
  19. @click="onClickCloseIcon"
  20. class="nutui-popup__close-icon"
  21. :class="'nutui-popup__close-icon--' + closeIconPosition"
  22. >
  23. <nut-icon v-bind="$attrs" :name="closeIcon" size="12px" />
  24. </view>
  25. </view>
  26. </Transition>
  27. </Teleport>
  28. <view v-else>
  29. <nut-overlay
  30. v-if="overlay"
  31. :visible="visible"
  32. :close-on-click-overlay="closeOnClickOverlay"
  33. :class="overlayClass"
  34. :style="overlayStyle"
  35. :z-index="zIndex"
  36. :lock-scroll="lockScroll"
  37. :duration="duration"
  38. @click="onClickOverlay"
  39. />
  40. <Transition :name="transitionName" @after-enter="onOpened" @after-leave="onClosed">
  41. <view v-show="visible" :class="classes" :style="popStyle" @click="onClick">
  42. <slot v-if="showSlot"></slot>
  43. <view
  44. v-if="closed"
  45. @click="onClickCloseIcon"
  46. class="nutui-popup__close-icon"
  47. :class="'nutui-popup__close-icon--' + closeIconPosition"
  48. >
  49. <nut-icon :name="closeIcon" size="12px" />
  50. </view>
  51. </view>
  52. </Transition>
  53. </view>
  54. </template>
  55. <script lang="ts">
  56. import {
  57. onMounted,
  58. onBeforeMount,
  59. onBeforeUnmount,
  60. onActivated,
  61. onDeactivated,
  62. watch,
  63. computed,
  64. reactive,
  65. PropType,
  66. CSSProperties,
  67. toRefs,
  68. ref
  69. } from 'vue';
  70. import { useLockScroll } from './use-lock-scroll';
  71. import { overlayProps } from './../overlay/index.vue';
  72. import overlay from '../overlay/index.vue';
  73. import icon from '../icon/index.vue';
  74. import { createComponent } from '@/packages/utils/create';
  75. const { componentName, create } = createComponent('popup');
  76. let _zIndex = 2000;
  77. export const popupProps = {
  78. ...overlayProps,
  79. position: {
  80. type: String,
  81. default: 'center'
  82. },
  83. transition: String,
  84. style: {
  85. type: Object as PropType<CSSProperties>
  86. },
  87. popClass: {
  88. type: String,
  89. default: ''
  90. },
  91. closeable: {
  92. type: Boolean,
  93. default: false
  94. },
  95. closeIconPosition: {
  96. type: String,
  97. default: 'top-right'
  98. },
  99. closeIcon: {
  100. type: String,
  101. default: 'close'
  102. },
  103. destroyOnClose: {
  104. type: Boolean,
  105. default: true
  106. },
  107. teleport: {
  108. type: [String, Element],
  109. default: 'body'
  110. },
  111. overlay: {
  112. type: Boolean,
  113. default: true
  114. },
  115. round: {
  116. type: Boolean,
  117. default: false
  118. },
  119. isWrapTeleport: {
  120. type: Boolean,
  121. default: true
  122. },
  123. safeAreaInsetBottom: {
  124. type: Boolean,
  125. default: false
  126. }
  127. };
  128. export default create({
  129. components: {
  130. [overlay.name]: overlay,
  131. [icon.name]: icon
  132. },
  133. props: {
  134. ...popupProps
  135. },
  136. emits: ['click', 'click-close-icon', 'open', 'close', 'opend', 'closed', 'update:visible', 'click-overlay'],
  137. setup(props, { emit }) {
  138. const popupRef = ref();
  139. const state = reactive({
  140. zIndex: props.zIndex,
  141. showSlot: true,
  142. transitionName: `popup-fade-${props.position}`,
  143. overLayCount: 1,
  144. keepAlive: false,
  145. closed: props.closeable
  146. });
  147. const [lockScroll, unlockScroll] = useLockScroll(() => props.lockScroll);
  148. const classes = computed(() => {
  149. const prefixCls = componentName;
  150. return {
  151. [prefixCls]: true,
  152. ['round']: props.round,
  153. [`popup-${props.position}`]: true,
  154. [`popup-${props.position}--safebottom`]: props.position === 'bottom' && props.safeAreaInsetBottom,
  155. [props.popClass]: true
  156. };
  157. });
  158. const popStyle = computed(() => {
  159. return {
  160. zIndex: state.zIndex,
  161. animationDuration: props.duration ? `${props.duration}s` : 'initial',
  162. ...props.style
  163. };
  164. });
  165. const open = () => {
  166. if (props.zIndex != 2000) {
  167. _zIndex = Number(props.zIndex);
  168. }
  169. emit('update:visible', true);
  170. lockScroll();
  171. state.zIndex = ++_zIndex;
  172. if (props.destroyOnClose) {
  173. state.showSlot = true;
  174. }
  175. emit('open');
  176. };
  177. const close = () => {
  178. unlockScroll();
  179. emit('update:visible', false);
  180. if (props.destroyOnClose) {
  181. setTimeout(() => {
  182. state.showSlot = false;
  183. emit('close');
  184. }, +props.duration * 1000);
  185. }
  186. };
  187. const onClick = (e: Event) => {
  188. emit('click', e);
  189. };
  190. const onClickCloseIcon = (e: Event) => {
  191. emit('click-close-icon', e);
  192. close();
  193. };
  194. const onClickOverlay = (e: Event) => {
  195. if (props.closeOnClickOverlay) {
  196. emit('click-overlay', e);
  197. close();
  198. }
  199. };
  200. const onOpened = (e: Event) => {
  201. emit('opend', e);
  202. };
  203. const onClosed = (e: Event) => {
  204. emit('closed', e);
  205. };
  206. onMounted(() => {
  207. props.transition
  208. ? (state.transitionName = props.transition)
  209. : (state.transitionName = `popup-slide-${props.position}`);
  210. props.visible && open();
  211. });
  212. onBeforeUnmount(() => {
  213. props.visible && close();
  214. });
  215. onBeforeMount(() => {
  216. if (props.visible) {
  217. unlockScroll();
  218. }
  219. });
  220. onActivated(() => {
  221. if (state.keepAlive) {
  222. emit('update:visible', true);
  223. state.keepAlive = false;
  224. }
  225. });
  226. onDeactivated(() => {
  227. if (props.visible) {
  228. close();
  229. state.keepAlive = true;
  230. }
  231. });
  232. watch(
  233. () => props.visible,
  234. (value) => {
  235. if (value) {
  236. open();
  237. } else {
  238. close();
  239. }
  240. }
  241. );
  242. watch(
  243. () => props.position,
  244. (value) => {
  245. value === 'center' ? (state.transitionName = 'popup-fade') : (state.transitionName = `popup-slide-${value}`);
  246. }
  247. );
  248. watch(
  249. () => props.closeable,
  250. (value) => {
  251. state.closed = value;
  252. }
  253. );
  254. return {
  255. ...toRefs(state),
  256. popStyle,
  257. classes,
  258. onClick,
  259. onClickCloseIcon,
  260. onClickOverlay,
  261. onOpened,
  262. onClosed,
  263. popupRef
  264. };
  265. }
  266. });
  267. </script>