wk-dropdown.vue 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376
  1. <template>
  2. <view class="wk-dropdown">
  3. <view class="wk-dropdown-menu">
  4. <template v-for="(menu, index) in menuList">
  5. <view
  6. :key="index"
  7. :class="{ active: activeMenuIndex === index }"
  8. :style="{
  9. color: activeMenuIndex === index ? activeColor : inactiveColor
  10. }"
  11. class="wk-dropdown-menu_item"
  12. @tap="handleMenuClick(index)">
  13. <text :class="menu.leftIcon" class="wk wk-dropdown-menu_item__left-icon" />
  14. <text class="wk-dropdown-menu_item__text">
  15. {{ menu.name }}
  16. </text>
  17. <text v-if="!menu.leftIcon" class="wk wk-arrow-right wk-dropdown-menu_item__icon" />
  18. </view>
  19. </template>
  20. </view>
  21. <view
  22. :style="{
  23. height: contentStyle.height + 'px',
  24. zIndex: contentStyle.zIndex
  25. }"
  26. class="wk-dropdown-content">
  27. <view
  28. :style="{
  29. transform: activeMenuIndex !== -1 ? 'translateY(0)' : 'translateY(-100%)',
  30. maxHeight: contentHeight * 0.75 + 'px'
  31. }"
  32. class="wk-dropdown-content__pop" @click.stop>
  33. <slot />
  34. </view>
  35. <view
  36. :style="{
  37. height: maskStyle.height,
  38. top: maskStyle.top,
  39. opacity: maskStyle.opacity,
  40. zIndex: maskStyle.zIndex
  41. }"
  42. class="wk-dropdown-content__mask"
  43. @tap.stop.prevent="handleMenuClick(-1)" />
  44. </view>
  45. </view>
  46. </template>
  47. <script>
  48. /**
  49. * WkDropdown 下拉菜单(配合 WkDropdownItem 使用)
  50. * @author yxk
  51. * @property {String} activeColor 标题和选项卡选中的颜色(默认#3C80F7)
  52. * @property {String} inactiveColor 标题和选项卡未选中的颜色(默认#666666)
  53. * @property {Boolean} autoClose DropdownItem为非自定义内容时选择选项后是否自动关闭(默认 false 不自动关闭)
  54. * @property {Function} beforeOpen 拦截下拉菜单打开 参数: index next
  55. * @event {Function} open 下拉菜单被打开时触发 参数: index
  56. * @event {Function} change 切换下拉菜单时触发 参数: index
  57. * @event {Function} close 下拉菜单全部被关闭时触发
  58. *
  59. * @tips: 关闭下拉选项需手动调用 close 方法
  60. *
  61. * @example
  62. * <wk-dropdown>
  63. * <wk-dropdown-item name="排序" :options="[]"></wk-dropdown-item>
  64. * <wk-dropdown-item name="自定义内容">
  65. * your slot content
  66. * </wk-dropdown-item>
  67. * </wk-dropdown>
  68. */
  69. export default {
  70. name: 'WkDropdown',
  71. provide() {
  72. return {
  73. dropdown: this
  74. }
  75. },
  76. props: {
  77. activeColor: {
  78. type: String,
  79. default: '#3C80F7'
  80. },
  81. inactiveColor: {
  82. type: String,
  83. default: '#666666'
  84. },
  85. beforeOpen: {
  86. type: Function,
  87. default: null
  88. },
  89. autoClose: {
  90. type: Boolean,
  91. default: false
  92. }
  93. },
  94. data() {
  95. return {
  96. activeMenuIndex: -1,
  97. menuList: [], // 菜单项
  98. contentStyle: {
  99. height: 0,
  100. zIndex: -1
  101. },
  102. contentHeight: 0,
  103. maskStyle: {
  104. height: 0,
  105. top: 0,
  106. opacity: 0,
  107. zIndex: 1
  108. },
  109. children: []
  110. }
  111. },
  112. created() {
  113. this.children = []
  114. },
  115. mounted() {
  116. this.$nextTick(() => {
  117. this.getContentHeight()
  118. })
  119. // console.log(this.children)
  120. // this.initTab()
  121. },
  122. methods: {
  123. /**
  124. * 点击menu
  125. * @param {Number} index
  126. */
  127. handleMenuClick(index) {
  128. if (this.beforeOpen) {
  129. const that = this
  130. this.beforeOpen(index, () => {
  131. that.open(index)
  132. })
  133. } else {
  134. this.open(index)
  135. }
  136. },
  137. getParentNode(name = undefined) {
  138. let parent = this.$parent;
  139. // 通过while历遍,这里主要是为了H5需要多层解析的问题
  140. while (parent) {
  141. // 父组件
  142. if (parent.$options && parent.$options.name !== name) {
  143. // 如果组件的name不相等,继续上一级寻找
  144. parent = parent.$parent;
  145. } else {
  146. return parent;
  147. }
  148. }
  149. return false;
  150. },
  151. addChild(node) {
  152. // console.log('add node: ', this)
  153. // const findRes = this.menuList.find(o => o.name === node.name)
  154. // if (findRes) {
  155. // findRes.instance = node
  156. // }
  157. },
  158. /**
  159. * 展开选项
  160. * @param {Number} openIndex
  161. */
  162. open(openIndex) {
  163. let showFlag = false
  164. const oldIndex = this.activeMenuIndex
  165. if (openIndex === this.activeMenuIndex) {
  166. this.activeMenuIndex = -1
  167. } else {
  168. this.activeMenuIndex = openIndex
  169. this.emitChangeEvt(openIndex)
  170. }
  171. this.$nextTick(() => {
  172. const children = this.getChildren()
  173. // this.children = children
  174. children.forEach((node, index) => {
  175. if (index === this.activeMenuIndex) {
  176. console.log('open node', node)
  177. node.opened = true
  178. showFlag = true
  179. } else if (index === oldIndex && this.activeMenuIndex === -1) {
  180. // 如果原来没有激活的索引则去执行关闭动画
  181. this.close(index)
  182. } else {
  183. // 直接关闭原来被激活的索引不执行动画
  184. node.opened = false
  185. }
  186. })
  187. if (showFlag) {
  188. this.$set(this.contentStyle, 'zIndex', 10)
  189. this.$set(this.maskStyle, 'opacity', 1)
  190. this.emitOpenEvt(this.activeMenuIndex)
  191. }
  192. })
  193. },
  194. getChildren() {
  195. let children = null
  196. // #ifdef MP-WEIXIN
  197. children = this.$children
  198. // #endif
  199. // #ifndef MP-WEIXIN
  200. children = this.$slots.default || []
  201. children = children.filter(o => Boolean(o.tag)).map(o => {
  202. return o.componentInstance
  203. })
  204. // #endif
  205. return children
  206. },
  207. /**
  208. * 执行关闭动画
  209. * @param {Number} index
  210. */
  211. close(index = -1) {
  212. this.$set(this.maskStyle, 'opacity', 0)
  213. let vm = null
  214. const children = this.getChildren()
  215. if (index !== -1) {
  216. vm = children[index]
  217. } else {
  218. vm = children.find(v => v.opened)
  219. this.activeMenuIndex = -1
  220. }
  221. if (!vm) {
  222. this.$set(this.contentStyle, 'zIndex', -1)
  223. return
  224. }
  225. // 动画执行完成后关闭延时关闭
  226. setTimeout(() => {
  227. vm.opend = false
  228. this.emitCloseEvt()
  229. this.$set(this.contentStyle, 'zIndex', -1)
  230. }, 300)
  231. },
  232. /**
  233. * 开启动画执行完毕后触发open事件
  234. * @param {Object} index
  235. */
  236. emitOpenEvt(index) {
  237. setTimeout(() => {
  238. this.$emit('open', index)
  239. }, 300)
  240. },
  241. /**
  242. * 切换下拉tab时触发
  243. * @param {Object} index
  244. */
  245. emitChangeEvt(index) {
  246. this.$emit('change', index)
  247. },
  248. /**
  249. * 关闭动画执行完成后触发close事件
  250. */
  251. emitCloseEvt() {
  252. this.$emit('close')
  253. },
  254. /**
  255. * 获取content内容高度
  256. */
  257. getContentHeight() {
  258. const that = this
  259. const windowHeight = uni.getSystemInfoSync().windowHeight
  260. uni.createSelectorQuery()
  261. .in(this)
  262. .select('.wk-dropdown')
  263. .boundingClientRect(res => {
  264. // console.log('getContentHeight: ', res)
  265. if (!res) return
  266. const height = windowHeight - res.bottom
  267. that.$set(that.contentStyle, 'height', height)
  268. that.maskStyle = {
  269. height: height + 'px',
  270. top: res.bottom + 'px',
  271. opacity: 0,
  272. zIndex: 1
  273. }
  274. this.contentHeight = height
  275. })
  276. .exec()
  277. }
  278. }
  279. }
  280. </script>
  281. <style scoped lang="scss">
  282. .wk-dropdown {
  283. position: relative;
  284. width: 100%;
  285. height: 80rpx;
  286. padding: 0 34rpx;
  287. @include center;
  288. .wk-dropdown-menu {
  289. width: 100%;
  290. display: flex;
  291. align-items: center;
  292. justify-content: space-between;
  293. &_item {
  294. flex: 1;
  295. font-size: 26rpx;
  296. @include center;
  297. &__text {
  298. line-height: 1;
  299. margin: 0 10rpx;
  300. }
  301. &__left-icon {
  302. font-size: 26rpx;
  303. align-content: center;
  304. }
  305. &__icon {
  306. transform: rotate(90deg);
  307. font-size: 24rpx;
  308. align-content: center;
  309. will-change: transform;
  310. transition: transform ease .3s;
  311. }
  312. }
  313. .wk-dropdown-menu_item.active {
  314. .wk-dropdown-menu_item__icon {
  315. transform: rotate(-90deg);
  316. }
  317. }
  318. }
  319. .wk-dropdown-content {
  320. position: absolute;
  321. top: 80rpx;
  322. left: 0;
  323. z-index: 11;
  324. width: 750rpx;
  325. font-size: 28rpx;
  326. overflow: hidden;
  327. &__pop {
  328. position: absolute;
  329. z-index: 100;
  330. width: 100%;
  331. background-color: white;
  332. border-top: 1rpx solid $border-color;
  333. will-change: transform;
  334. transition: transform ease 0.3s;
  335. transform: translateY(-100%);
  336. overflow: hidden;
  337. display: flex;
  338. flex-direction: column;
  339. }
  340. &__mask {
  341. position: fixed;
  342. bottom: 0;
  343. left: 0;
  344. width: 100%;
  345. background-color: rgba(0, 0, 0, .4);
  346. opacity: 0;
  347. will-change: opacity;
  348. transition: opacity ease 0.3s;
  349. }
  350. }
  351. }
  352. </style>