index.vue 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. <template>
  2. <view :class="classes">
  3. <view class="nut-menu__bar" :class="{ opened: opened }" ref="barRef">
  4. <template v-for="(item, index) in children" :key="index">
  5. <view
  6. class="nut-menu__item"
  7. @click="!item.disabled && toggleItem(index)"
  8. :class="{ disabled: item.disabled, active: item.state.showPopup }"
  9. :style="{ color: item.state.showPopup ? activeColor : '' }"
  10. >
  11. <view class="nut-menu__title" :class="getClasses(item.state.showPopup)">
  12. <view class="nut-menu__title-text">{{ item.renderTitle() }}</view>
  13. <nut-icon
  14. v-bind="$attrs"
  15. :name="titleIcon || (direction === 'up' ? 'arrow-up' : 'down-arrow')"
  16. size="10"
  17. class="nut-menu__title-icon"
  18. ></nut-icon>
  19. </view>
  20. </view>
  21. </template>
  22. </view>
  23. <slot></slot>
  24. </view>
  25. </template>
  26. <script lang="ts">
  27. import { reactive, provide, computed, ref, onMounted, onUnmounted } from 'vue';
  28. import { createComponent } from '@/packages/utils/create';
  29. import { useRect } from '@/packages/utils/useRect';
  30. const { componentName, create } = createComponent('menu');
  31. export default create({
  32. props: {
  33. activeColor: {
  34. type: String,
  35. default: ''
  36. },
  37. overlay: {
  38. type: Boolean,
  39. default: true as const
  40. },
  41. lockScroll: {
  42. type: Boolean,
  43. default: true as const
  44. },
  45. duration: {
  46. type: [Number, String],
  47. default: 0
  48. },
  49. titleIcon: String,
  50. closeOnClickOverlay: {
  51. type: Boolean,
  52. default: true
  53. },
  54. direction: {
  55. type: String,
  56. default: 'down'
  57. },
  58. scrollFixed: {
  59. type: [Boolean, String, Number],
  60. default: false
  61. },
  62. titleClass: [String]
  63. },
  64. setup(props, { emit, slots }) {
  65. const barRef = ref<HTMLElement>();
  66. const offset = ref(0);
  67. const isScrollFixed = ref(false);
  68. const useChildren = () => {
  69. const publicChildren: any[] = reactive([]);
  70. const internalChildren: any[] = reactive([]);
  71. const linkChildren = (value?: any) => {
  72. const link = (child: any) => {
  73. if (child.proxy) {
  74. internalChildren.push(child);
  75. publicChildren.push(child.proxy as any);
  76. }
  77. };
  78. const removeLink = (child: any) => {
  79. if (child.proxy) {
  80. let internalIndex = internalChildren.indexOf(child);
  81. if (internalIndex > -1) {
  82. internalChildren.splice(internalIndex, 1);
  83. }
  84. let publicIndex = publicChildren.indexOf(child.proxy);
  85. if (internalIndex > -1) {
  86. publicChildren.splice(publicIndex, 1);
  87. }
  88. }
  89. };
  90. provide(
  91. 'menuParent',
  92. Object.assign(
  93. {
  94. removeLink,
  95. link,
  96. children: publicChildren,
  97. internalChildren
  98. },
  99. value
  100. )
  101. );
  102. };
  103. return {
  104. children: publicChildren,
  105. linkChildren
  106. };
  107. };
  108. const { children, linkChildren } = useChildren();
  109. const opened = computed(() => children.some((item) => item.state.showWrapper));
  110. const classes = computed(() => {
  111. const prefixCls = componentName;
  112. return {
  113. [prefixCls]: true,
  114. 'scroll-fixed': isScrollFixed.value
  115. };
  116. });
  117. const updateOffset = () => {
  118. if (barRef.value) {
  119. const rect = useRect(barRef);
  120. if (props.direction === 'down') {
  121. offset.value = rect.bottom;
  122. } else {
  123. offset.value = window.innerHeight - rect.top;
  124. }
  125. }
  126. };
  127. linkChildren({ props, offset });
  128. const toggleItem = (active: number) => {
  129. children.forEach((item, index) => {
  130. if (index === active) {
  131. updateOffset();
  132. item.toggle();
  133. } else if (item.state.showPopup) {
  134. item.toggle(false, { immediate: true });
  135. }
  136. });
  137. };
  138. const getScrollTop = (el: Element | Window) => {
  139. return Math.max(0, 'scrollTop' in el ? el.scrollTop : el.pageYOffset);
  140. };
  141. const onScroll = () => {
  142. const { scrollFixed } = props;
  143. const scrollTop = getScrollTop(window);
  144. isScrollFixed.value = scrollTop > (typeof scrollFixed === 'boolean' ? 30 : Number(scrollFixed));
  145. };
  146. const getClasses = (showPopup: boolean) => {
  147. let str = '';
  148. const { titleClass } = props;
  149. if (showPopup) {
  150. str += 'active';
  151. }
  152. if (titleClass) {
  153. str += ` ${titleClass}`;
  154. }
  155. return str;
  156. };
  157. onMounted(() => {
  158. const { scrollFixed } = props;
  159. if (scrollFixed) {
  160. window.addEventListener('scroll', onScroll);
  161. }
  162. });
  163. onUnmounted(() => {
  164. const { scrollFixed } = props;
  165. if (scrollFixed) {
  166. window.removeEventListener('scroll', onScroll);
  167. }
  168. });
  169. return {
  170. toggleItem,
  171. children,
  172. opened,
  173. classes,
  174. barRef,
  175. getClasses
  176. };
  177. }
  178. });
  179. </script>