popup.vue 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. <template>
  2. <transition
  3. :name="transitionName"
  4. @after-enter="$emit('opened')"
  5. @after-leave="$emit('closed')"
  6. >
  7. <div
  8. ref="popupBox"
  9. v-show="value"
  10. :style="{animationDuration:transitionDuration}"
  11. class="popup-box"
  12. :class="[`popup-${position}`, { round }]"
  13. @click="$emit('click', this)"
  14. >
  15. <slot v-if="showSlot"></slot>
  16. <icon
  17. v-if="closeable"
  18. @click.native="$emit('input', false)"
  19. :type="closeIcon"
  20. size="12px"
  21. class="nutui-popup__close-icon"
  22. :class="'nutui-popup__close-icon--' + closeIconPosition"
  23. >
  24. </icon>
  25. </div>
  26. </transition>
  27. </template>
  28. <script>
  29. import Vue from "vue";
  30. import overlay from "./overlay.vue";
  31. import Icon from '../icon/icon.vue';
  32. import '../icon/icon.scss';
  33. export default {
  34. name: "nut-popup",
  35. components:{
  36. "icon":Icon
  37. },
  38. props: {
  39. value: {
  40. type: Boolean,
  41. default: false
  42. },
  43. position: {
  44. type: String,
  45. default: "center"
  46. },
  47. duration: {
  48. type: Number,
  49. default:0.3
  50. },
  51. transition: String,
  52. overlay: {
  53. type: Boolean,
  54. default: true
  55. },
  56. closeable: {
  57. type: Boolean,
  58. default: false
  59. },
  60. closeIconPosition: {
  61. type: String,
  62. default: "top-right"
  63. },
  64. closeIcon: {
  65. type: String,
  66. default: "cross"
  67. },
  68. lockScroll:{
  69. type:Boolean,
  70. default:true
  71. },
  72. closeOnClickOverlay:{
  73. type:Boolean,
  74. default:true
  75. },
  76. overlayClass: {
  77. type:String,
  78. default:""
  79. },
  80. overlayStyle: {
  81. type:String,
  82. default:""
  83. },
  84. destroyOnClose:{
  85. type: Boolean,
  86. default: false
  87. },
  88. getContainer:String,
  89. round: {
  90. type: Boolean,
  91. default: false
  92. }
  93. },
  94. created() {
  95. this.transition ? this.transitionName = this.transition :this.transitionName = `popup-slide-${this.position}`;
  96. },
  97. mounted() {
  98. this.mountOverlay();
  99. if (this.getContainer) {
  100. this.portal();
  101. }
  102. if (this.value) {
  103. this.open();
  104. }
  105. },
  106. watch: {
  107. value(val) {
  108. const type = val ? "open" : "close";
  109. if (this.overlay) {
  110. this[type]();
  111. }
  112. },
  113. position(val) {
  114. val === "center" ? this.transitionName = "popup-fade" :this.transitionName = `popup-slide-${this.position}`;
  115. },
  116. getContainer: 'portal'
  117. },
  118. data() {
  119. return {
  120. showSlot:true,
  121. transitionName: "popup-fade-center",
  122. overlayInstant: null
  123. };
  124. },
  125. computed:{
  126. transitionDuration(){
  127. return this.duration ? this.duration + 's' : 'initial';
  128. }
  129. },
  130. methods: {
  131. mountOverlay(){
  132. if (!this.overlayInstant) {
  133. this.overlayInstant = this.mount(overlay, {
  134. duration: this.duration,
  135. nativeOn: {
  136. click: () => {
  137. this.$emit("click-overlay", this);
  138. if(this.closeOnClickOverlay){
  139. this.$emit("input", false);
  140. }
  141. }
  142. }
  143. });
  144. }
  145. },
  146. mount(Component, data) {
  147. const instance = new Vue({
  148. el: document.createElement("div"),
  149. props: Component.props,
  150. render(h) {
  151. return h(Component, {
  152. props: this.$props,
  153. ...data
  154. });
  155. }
  156. });
  157. instance.duration = this.duration;
  158. instance.lockScroll = this.lockScroll;
  159. instance.className = this.overlayClass;
  160. instance.customStyle = this.overlayStyle;
  161. const el = this.$refs.popupBox;
  162. if (el && el.parentNode) {
  163. el.parentNode.insertBefore(instance.$el, el);
  164. } else {
  165. document.body.appendChild(instance.$el);
  166. }
  167. return instance;
  168. },
  169. open() {
  170. if (!this.overlayInstant) {
  171. this.mountOverlay();
  172. } else {
  173. this.overlayInstant.show = true;
  174. this.showSlot = true;
  175. }
  176. if (this.lockScroll && !this.locked) {
  177. document.body.classList.add('nut-overflow-hidden');
  178. this.locked = true;
  179. }
  180. this.$emit("open", this);
  181. },
  182. close() {
  183. this.overlayInstant.show = false;
  184. if(this.destroyOnClose){
  185. setTimeout(()=>{
  186. this.showSlot = false;
  187. }, this.duration * 1000)
  188. }
  189. if (this.lockScroll && this.locked) {
  190. document.body.classList.remove('nut-overflow-hidden');
  191. this.locked = false;
  192. }
  193. this.$emit("close", this);
  194. },
  195. getElement(selector){
  196. return document.querySelector(selector);
  197. },
  198. portal() {
  199. const { getContainer } = this;
  200. const el = this.$el;
  201. let container;
  202. if (getContainer) {
  203. container = this.getElement(getContainer);
  204. } else if (this.$parent) {
  205. container = this.$parent.$el;
  206. }
  207. if (container && container !== el.parentNode) {
  208. container.appendChild(el);
  209. }
  210. }
  211. }
  212. };
  213. </script>