u-popup.vue 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <template>
  2. <view v-if="visibleSync" :style="[customStyle]" :class="{ 'u-drawer-visible': showDrawer }" class="u-drawer" >
  3. <u-mask :maskClickAble="maskCloseAble" :show="showDrawer && mask" @click="maskClick"></u-mask>
  4. <view
  5. class="u-drawer-content"
  6. @tap="modeCenterClose(mode)"
  7. :class="[
  8. safeAreaInsetBottom ? 'safe-area-inset-bottom' : '',
  9. 'u-drawer-' + mode,
  10. showDrawer ? 'u-drawer-content-visible' : '',
  11. zoom && mode == 'center' ? 'u-animation-zoom' : ''
  12. ]"
  13. :style="[style]"
  14. >
  15. <view class="u-mode-center-box" @tap.stop v-if="mode == 'center'" :style="[centerStyle]"><slot /></view>
  16. <block v-else><slot /></block>
  17. </view>
  18. </view>
  19. </template>
  20. <script>
  21. import uMask from '../u-mask/u-mask.vue';
  22. export default {
  23. name: 'uDrawer',
  24. props: {
  25. /**
  26. * 显示状态
  27. */
  28. show: {
  29. type: Boolean,
  30. default: false
  31. },
  32. /**
  33. * 弹出方向,left|right|top|bottom|center
  34. */
  35. mode: {
  36. type: String,
  37. default: 'left'
  38. },
  39. /**
  40. * 是否显示遮罩
  41. */
  42. mask: {
  43. type: Boolean,
  44. default: true
  45. },
  46. // 抽屉的宽度(mode=left|right),或者高度(mode=top|bottom),单位rpx,或者"auto"
  47. // 或者百分比"50%",表示由内容撑开高度或者宽度
  48. length: {
  49. type: [Number, String],
  50. default: 'auto'
  51. },
  52. // 是否开启缩放动画,只在mode=center时有效
  53. zoom: {
  54. type: Boolean,
  55. default: true
  56. },
  57. // 是否开启底部安全区适配,开启的话,会在iPhoneX机型底部添加一定的内边距
  58. safeAreaInsetBottom: {
  59. type: Boolean,
  60. default: false
  61. },
  62. // 是否可以通过点击遮罩进行关闭
  63. maskCloseAble: {
  64. type: Boolean,
  65. default: true
  66. },
  67. // 用户自定义样式
  68. customStyle: {
  69. type: Object,
  70. default() {
  71. return {};
  72. }
  73. },
  74. value: {
  75. type: Boolean,
  76. default: false
  77. },
  78. // 此为内部参数,不在文档对外使用,为了解决Picker和keyboard等融合了弹窗的组件
  79. // 对v-model双向绑定多层调用造成报错不能修改props值的问题
  80. popup: {
  81. type: Boolean,
  82. default: true
  83. },
  84. // 显示显示弹窗的圆角,单位rpx
  85. borderRadius: {
  86. type: [Number, String],
  87. default: 0
  88. }
  89. },
  90. data() {
  91. return {
  92. visibleSync: false,
  93. showDrawer: false,
  94. timer: null,
  95. style1: {}
  96. };
  97. },
  98. computed: {
  99. // 根据mode的位置,设定其弹窗的宽度(mode = left|right),或者高度(mode = top|bottom)
  100. style() {
  101. let style = {};
  102. let translate = '100%';
  103. // 判断是否是否百分比或者auto值,是的话,直接使用该值,否则默认为rpx单位的数值
  104. let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px';
  105. // 如果是左边或者上边弹出时,需要给translate设置为负值,用于隐藏
  106. if (this.mode == 'left' || this.mode == 'top') translate = length == 'auto' ? '-100%' : '-' + length;
  107. if (this.mode == 'left' || this.mode == 'right') {
  108. style = {
  109. width: length,
  110. height: '100%',
  111. transform: `translate3D(${translate},0px,0px)`
  112. };
  113. } else if (this.mode == 'top' || this.mode == 'bottom') {
  114. style = {
  115. width: '100%',
  116. height: length,
  117. transform: `translate3D(0px,${translate},0px)`
  118. };
  119. }
  120. // 如果用户设置了borderRadius值,添加弹窗的圆角
  121. if (this.borderRadius) {
  122. switch (this.mode) {
  123. case 'left':
  124. style.borderRadius = `0 ${this.borderRadius}rpx ${this.borderRadius}rpx 0`;
  125. break;
  126. case 'top':
  127. style.borderRadius = `0 0 ${this.borderRadius}rpx ${this.borderRadius}rpx`;
  128. break;
  129. case 'right':
  130. style.borderRadius = `${this.borderRadius}rpx 0 0 ${this.borderRadius}rpx`;
  131. break;
  132. case 'bottom':
  133. style.borderRadius = `${this.borderRadius}rpx ${this.borderRadius}rpx 0 0`;
  134. break;
  135. default: ;
  136. }
  137. // 不加可能圆角无效
  138. // style.overflow = 'hidden';
  139. }
  140. return style;
  141. },
  142. // 中部弹窗的特有样式
  143. centerStyle() {
  144. let style = {};
  145. let length = (/%$/.test(this.length) || this.length == 'auto') ? this.length : uni.upx2px(this.length) + 'px';
  146. style.width = length;
  147. if(this.borderRadius) {
  148. style.borderRadius = `${this.borderRadius}rpx`;
  149. // // 不加可能圆角无效
  150. style.overflow = 'hidden';
  151. }
  152. return style;
  153. }
  154. },
  155. watch: {
  156. value(val) {
  157. if (val) {
  158. this.open();
  159. } else {
  160. this.close();
  161. }
  162. }
  163. },
  164. created() {
  165. // 先让弹窗组件渲染,再改变遮罩和抽屉元素的样式,让其动画其起作用(必须要有延时,才会有效果)
  166. this.visibleSync = this.value;
  167. setTimeout(() => {
  168. this.showDrawer = this.value;
  169. }, 30);
  170. },
  171. methods: {
  172. // 遮罩被点击
  173. maskClick() {
  174. this.close();
  175. },
  176. close() {
  177. this.change('showDrawer', 'visibleSync', false);
  178. },
  179. // 中部弹出时,需要.u-drawer-content将居中内容,此元素会铺满屏幕,点击需要关闭弹窗
  180. // 让其只在mode=center时起作用
  181. modeCenterClose(mode) {
  182. if (mode != 'center' || !this.maskCloseAble) return;
  183. this.close();
  184. },
  185. open() {
  186. this.change('visibleSync', 'showDrawer', true);
  187. },
  188. // 此处的原理是,关闭时先通过动画隐藏弹窗和遮罩,再移除整个组件
  189. // 打开时,先渲染组件,延时一定时间再让遮罩和弹窗的动画起作用
  190. change(param1, param2, status) {
  191. // 如果this.popup为false,以为着为picker,actionsheet等组件调用了popup组件
  192. if (this.popup == true) this.$emit('input', status);
  193. this[param1] = status;
  194. if (this.timer) {
  195. clearTimeout(this.timer);
  196. }
  197. this.timer = setTimeout(
  198. () => {
  199. this[param2] = status;
  200. this.$emit(status ? 'open' : 'close');
  201. },
  202. status ? 30 : 300
  203. );
  204. }
  205. },
  206. components: {
  207. uMask
  208. }
  209. };
  210. </script>
  211. <style scoped lang="scss">
  212. .u-drawer {
  213. /* #ifndef APP-NVUE */
  214. display: block;
  215. /* #endif */
  216. position: fixed;
  217. top: 0;
  218. left: 0;
  219. right: 0;
  220. bottom: 0;
  221. overflow: hidden;
  222. z-index: 1999;
  223. }
  224. .u-drawer-content {
  225. /* #ifndef APP-NVUE */
  226. display: block;
  227. /* #endif */
  228. position: absolute;
  229. z-index: 1003;
  230. transition: all 0.25s linear;
  231. }
  232. .u-drawer-left {
  233. top: 0;
  234. bottom: 0;
  235. left: 0;
  236. background-color: #ffffff;
  237. }
  238. .u-drawer-right {
  239. right: 0;
  240. top: 0;
  241. bottom: 0;
  242. background-color: #ffffff;
  243. }
  244. .u-drawer-top {
  245. top: 0;
  246. left: 0;
  247. right: 0;
  248. background-color: #ffffff;
  249. }
  250. .u-drawer-bottom {
  251. bottom: 0;
  252. left: 0;
  253. right: 0;
  254. background-color: #ffffff;
  255. }
  256. .u-drawer-center {
  257. /* #ifndef APP-NVUE */
  258. display: flex;
  259. flex-direction: column;
  260. /* #endif */
  261. bottom: 0;
  262. left: 0;
  263. right: 0;
  264. top: 0;
  265. justify-content: center;
  266. align-items: center;
  267. opacity: 0;
  268. z-index: 99999;
  269. }
  270. .u-mode-center-box {
  271. min-width: 100rpx;
  272. min-height: 100rpx;
  273. /* #ifndef APP-NVUE */
  274. display: block;
  275. /* #endif */
  276. position: relative;
  277. }
  278. .u-drawer-content-visible.u-drawer-center {
  279. transform: scale(1);
  280. opacity: 1;
  281. }
  282. .u-animation-zoom {
  283. transform: scale(1.15);
  284. }
  285. .u-drawer-content-visible {
  286. transform: translate3D(0px, 0px, 0px) !important;
  287. }
  288. .u-drawer-mask {
  289. /* #ifndef APP-NVUE */
  290. display: block;
  291. /* #endif */
  292. opacity: 0;
  293. position: absolute;
  294. top: 0;
  295. left: 0;
  296. bottom: 0;
  297. right: 0;
  298. background-color: rgba(0, 0, 0, 0.4);
  299. transition: opacity 0.25s;
  300. }
  301. .u-drawer-mask-visible {
  302. /* #ifndef APP-NVUE */
  303. display: block;
  304. /* #endif */
  305. opacity: 1;
  306. }
  307. </style>