WDrawer.vue 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. <template>
  2. <div v-transfer-dom :data-transfer="transfer">
  3. <transition name="fade">
  4. <div :class="maskClasses" :style="maskStyle" v-show="visible" v-if="mask" @click="handleMask"></div>
  5. </transition>
  6. <div :class="wrapClasses" @click="handleWrapClick">
  7. <transition :name="'move-' + placement">
  8. <div :class="classes" :style="mainStyles" v-show="visible">
  9. <div :class="contentClasses" ref="content">
  10. <a class="ivu-drawer-close" v-if="closable" @click="close">
  11. <slot name="close">
  12. <Icon type="ios-close"></Icon>
  13. </slot>
  14. </a>
  15. <div :class="[prefixCls + '-header']" v-if="showHead"><slot name="header"><div :class="[prefixCls + '-header-inner']">{{ title }}</div></slot></div>
  16. <div :class="[prefixCls + '-body']" :style="styles"><slot></slot></div>
  17. </div>
  18. <div class="ivu-drawer-drag" :class="{ 'ivu-drawer-drag-left': placement === 'left' }" v-if="draggable" @mousedown="handleTriggerMousedown">
  19. <slot name="trigger">
  20. <div class="ivu-drawer-drag-move-trigger">
  21. <div class="ivu-drawer-drag-move-trigger-point">
  22. <i></i><i></i><i></i><i></i><i></i>
  23. </div>
  24. </div>
  25. </slot>
  26. </div>
  27. </div>
  28. </transition>
  29. </div>
  30. </div>
  31. </template>
  32. <script>
  33. import Icon from 'view-design/src/components/icon';
  34. import { oneOf, findBrothersComponents, findComponentsUpward } from 'view-design/src/utils/assist';
  35. import TransferDom from 'view-design/src/directives/transfer-dom';
  36. import Emitter from 'view-design/src/mixins/emitter';
  37. import ScrollbarMixins from 'view-design/src/components/modal/mixins-scrollbar';
  38. import { on, off } from 'view-design/src/utils/dom';
  39. const prefixCls = 'ivu-drawer';
  40. export default {
  41. name: 'WDrawer',
  42. mixins: [ Emitter, ScrollbarMixins ],
  43. components: { Icon },
  44. directives: { TransferDom },
  45. props: {
  46. value: {
  47. type: Boolean,
  48. default: false
  49. },
  50. title: {
  51. type: String
  52. },
  53. width: {
  54. type: [Number, String],
  55. default: '100%'
  56. },
  57. minWidth: {
  58. type: [Number, String],
  59. default: 0
  60. },
  61. maxWidth: {
  62. type: [Number, String],
  63. default: 0
  64. },
  65. closable: {
  66. type: Boolean,
  67. default: true
  68. },
  69. maskClosable: {
  70. type: Boolean,
  71. default: true
  72. },
  73. mask: {
  74. type: Boolean,
  75. default: true
  76. },
  77. maskStyle: {
  78. type: Object
  79. },
  80. styles: {
  81. type: Object
  82. },
  83. scrollable: {
  84. type: Boolean,
  85. default: false
  86. },
  87. placement: {
  88. validator (value) {
  89. return oneOf(value, ['left', 'right']);
  90. },
  91. default: 'right'
  92. },
  93. zIndex: {
  94. type: Number,
  95. default: 1000
  96. },
  97. transfer: {
  98. type: Boolean,
  99. default () {
  100. return !this.$IVIEW || this.$IVIEW.transfer === '' ? true : this.$IVIEW.transfer;
  101. }
  102. },
  103. className: {
  104. type: String
  105. },
  106. inner: {
  107. type: Boolean,
  108. default: false
  109. },
  110. // Whether drag and drop is allowed to adjust width
  111. draggable: {
  112. type: Boolean,
  113. default: false
  114. },
  115. beforeClose: Function,
  116. },
  117. data () {
  118. return {
  119. prefixCls: prefixCls,
  120. visible: this.value,
  121. wrapShow: false,
  122. showHead: true,
  123. canMove: false,
  124. dragWidth: this.width,
  125. wrapperWidth: this.width,
  126. wrapperLeft: 0,
  127. minHandleWidth: 256
  128. };
  129. },
  130. computed: {
  131. wrapClasses () {
  132. return [
  133. `${prefixCls}-wrap`,
  134. {
  135. [`${prefixCls}-hidden`]: !this.wrapShow,
  136. [`${this.className}`]: !!this.className,
  137. [`${prefixCls}-no-mask`]: !this.mask,
  138. [`${prefixCls}-wrap-inner`]: this.inner,
  139. [`${prefixCls}-wrap-dragging`]: this.canMove
  140. }
  141. ];
  142. },
  143. mainStyles () {
  144. let style = {};
  145. const minWidth = parseInt(this.minWidth);
  146. if (minWidth > 0) {
  147. style.minWidth = minWidth <= 100 ? `${minWidth}%` : `${minWidth}px`;
  148. }
  149. const maxWidth = parseInt(this.maxWidth);
  150. if (maxWidth > 0) {
  151. style.maxWidth = maxWidth <= 100 ? `${maxWidth}%` : `${maxWidth}px`;
  152. }
  153. const width = parseInt(this.dragWidth);
  154. const styleWidth = {
  155. width: width <= 100 ? `${width}%` : `${width}px`
  156. };
  157. Object.assign(style, styleWidth);
  158. return style;
  159. },
  160. contentClasses () {
  161. return [
  162. `${prefixCls}-content`,
  163. {
  164. [`${prefixCls}-content-no-mask`]: !this.mask
  165. }
  166. ];
  167. },
  168. classes () {
  169. return [
  170. `${prefixCls}`,
  171. `${prefixCls}-${this.placement}`,
  172. {
  173. [`${prefixCls}-no-header`]: !this.showHead,
  174. [`${prefixCls}-inner`]: this.inner
  175. }
  176. ];
  177. },
  178. maskClasses () {
  179. return [
  180. `${prefixCls}-mask`,
  181. {
  182. [`${prefixCls}-mask-inner`]: this.inner
  183. }
  184. ];
  185. }
  186. },
  187. methods: {
  188. close () {
  189. if (!this.beforeClose) {
  190. return this.handleClose();
  191. }
  192. const before = this.beforeClose();
  193. if (before && before.then) {
  194. before.then(() => {
  195. this.handleClose();
  196. });
  197. } else {
  198. this.handleClose();
  199. }
  200. },
  201. handleClose () {
  202. this.visible = false;
  203. this.$emit('input', false);
  204. this.$emit('on-close');
  205. },
  206. handleMask () {
  207. if (this.maskClosable && this.mask) {
  208. this.close();
  209. }
  210. },
  211. handleWrapClick (event) {
  212. // use indexOf,do not use === ,because ivu-modal-wrap can have other custom className
  213. const className = event.target.getAttribute('class');
  214. if (className && className.indexOf(`${prefixCls}-wrap`) > -1) this.handleMask();
  215. },
  216. handleMousemove (event) {
  217. if (!this.canMove || !this.draggable) return;
  218. // 更新容器宽度和距离左侧页面距离,如果是window则距左侧距离为0
  219. this.handleSetWrapperWidth();
  220. const left = event.pageX - this.wrapperLeft;
  221. // 如果抽屉方向为右边,宽度计算需用容器宽度减去left
  222. let width = this.placement === 'right' ? this.wrapperWidth - left : left;
  223. // 限定最小宽度
  224. width = Math.max(width, parseFloat(this.minHandleWidth));
  225. event.atMin = width === parseFloat(this.minHandleWidth);
  226. // 如果当前width不大于100,视为百分比
  227. if (width <= 100) width = (width / this.wrapperWidth) * 100;
  228. this.dragWidth = width;
  229. this.$emit('on-resize-width', parseInt(this.dragWidth));
  230. },
  231. handleSetWrapperWidth () {
  232. const {
  233. width,
  234. left
  235. } = this.$el.getBoundingClientRect();
  236. this.wrapperWidth = width;
  237. this.wrapperLeft = left;
  238. },
  239. handleMouseup () {
  240. if (!this.draggable) return;
  241. this.canMove = false;
  242. },
  243. handleTriggerMousedown () {
  244. this.canMove = true;
  245. // 防止鼠标选中抽屉中文字,造成拖动trigger触发浏览器原生拖动行为
  246. window.getSelection().removeAllRanges();
  247. },
  248. },
  249. mounted () {
  250. if (this.visible) {
  251. this.wrapShow = true;
  252. }
  253. let showHead = true;
  254. if (this.$slots.header === undefined && !this.title) {
  255. showHead = false;
  256. }
  257. this.showHead = showHead;
  258. on(document, 'mousemove', this.handleMousemove);
  259. on(document, 'mouseup', this.handleMouseup);
  260. this.handleSetWrapperWidth();
  261. },
  262. beforeDestroy () {
  263. off(document, 'mousemove', this.handleMousemove);
  264. off(document, 'mouseup', this.handleMouseup);
  265. this.removeScrollEffect();
  266. },
  267. watch: {
  268. value (val) {
  269. this.visible = val;
  270. },
  271. visible (val) {
  272. if (val === false) {
  273. this.timer = setTimeout(() => {
  274. this.wrapShow = false;
  275. // #4831 Check if there are any drawers left at the parent level
  276. const brotherDrawers = findBrothersComponents(this, 'Drawer') || [];
  277. const parentDrawers = findComponentsUpward(this, 'Drawer') || [];
  278. const otherDrawers = [].concat(brotherDrawers).concat(parentDrawers);
  279. const isScrollDrawer = otherDrawers.some(item => item.visible && !item.scrollable);
  280. if (!isScrollDrawer) {
  281. this.removeScrollEffect();
  282. }
  283. }, 300);
  284. } else {
  285. if (this.timer) clearTimeout(this.timer);
  286. this.wrapShow = true;
  287. if (!this.scrollable) {
  288. this.addScrollEffect();
  289. }
  290. }
  291. this.broadcast('Table', 'on-visible-change', val);
  292. this.broadcast('Slider', 'on-visible-change', val); // #2852
  293. this.$emit('on-visible-change', val);
  294. },
  295. scrollable (val) {
  296. if (!val) {
  297. this.addScrollEffect();
  298. } else {
  299. this.removeScrollEffect();
  300. }
  301. },
  302. title (val) {
  303. if (this.$slots.header === undefined) {
  304. this.showHead = !!val;
  305. }
  306. },
  307. width (val) {
  308. this.dragWidth = val;
  309. }
  310. }
  311. };
  312. </script>