wd-drop-menu-item.vue 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <view
  3. v-if="showWrapper"
  4. :class="`wd-drop-item ${customClass}`"
  5. :style="`pointer-events: none; z-index: ${zIndex}; ${positionStyle};${customStyle}`"
  6. >
  7. <wd-popup
  8. v-model="showPop"
  9. :z-index="zIndex"
  10. :duration="duration"
  11. :position="position"
  12. :custom-style="`position: absolute; pointer-events: auto; max-height: ${popupHeight ? popupHeight : '80%'}; ${customPopupStyle}`"
  13. :custom-class="customPopupClass"
  14. :modal="false"
  15. :close-on-click-modal="false"
  16. :root-portal="rootPortal"
  17. @before-enter="beforeEnter"
  18. @after-enter="afterEnter"
  19. @before-leave="beforeLeave"
  20. @after-leave="afterLeave"
  21. >
  22. <scroll-view v-if="options.length" :style="popupHeight ? { height: popupHeight } : ''" scroll-y scroll-with-animation :show-scrollbar="true">
  23. <view
  24. v-for="(item, index) in options"
  25. :key="index"
  26. @click="choose(index)"
  27. :class="`wd-drop-item__option ${(item[valueKey] !== '' ? item[valueKey] : item) === modelValue ? 'is-active' : ''}`"
  28. >
  29. <view :class="`wd-drop-item__title ${customTitle}`">
  30. <text>{{ item[labelKey] ? item[labelKey] : item }}</text>
  31. <text v-if="item[tipKey]" class="wd-drop-item__tip">{{ item[tipKey] }}</text>
  32. </view>
  33. <wd-icon
  34. v-if="(item[valueKey] !== '' ? item[valueKey] : item) === modelValue"
  35. :name="iconName"
  36. :custom-class="`wd-drop-item__icon ${customIcon}`"
  37. />
  38. </view>
  39. </scroll-view>
  40. <slot v-else />
  41. </wd-popup>
  42. </view>
  43. </template>
  44. <script lang="ts">
  45. export default {
  46. name: 'wd-drop-menu-item',
  47. options: {
  48. virtualHost: true,
  49. addGlobalClass: true,
  50. styleIsolation: 'shared'
  51. }
  52. }
  53. </script>
  54. <script lang="ts" setup>
  55. import wdPopup from '../wd-popup/wd-popup.vue'
  56. import wdIcon from '../wd-icon/wd-icon.vue'
  57. import { computed, getCurrentInstance, inject, onBeforeMount, onBeforeUnmount, ref, watch } from 'vue'
  58. import { pushToQueue, removeFromQueue } from '../common/clickoutside'
  59. import { type Queue, queueKey } from '../composables/useQueue'
  60. import type { PopupType } from '../wd-popup/types'
  61. import { useParent } from '../composables/useParent'
  62. import { DROP_MENU_KEY } from '../wd-drop-menu/types'
  63. import { isDef, isFunction } from '../common/util'
  64. import { dorpMenuItemProps, type DropMenuItemExpose } from './types'
  65. const props = defineProps(dorpMenuItemProps)
  66. const emit = defineEmits<{
  67. (e: 'update:modelValue', value: string | number): void
  68. (e: 'change', event: { value: string | number; selectedItem: Record<string, any> }): void
  69. (e: 'open'): void
  70. (e: 'opened'): void
  71. (e: 'close'): void
  72. (e: 'closed'): void
  73. }>()
  74. const queue = inject<Queue | null>(queueKey, null)
  75. const showWrapper = ref<boolean>(false)
  76. const showPop = ref<boolean>(false)
  77. const position = ref<PopupType>()
  78. const zIndex = ref<number>(12)
  79. const duration = ref<number>(0)
  80. const { parent: dropMenu } = useParent(DROP_MENU_KEY)
  81. const { proxy } = getCurrentInstance() as any
  82. const positionStyle = computed(() => {
  83. let style: string = ''
  84. if (showWrapper.value && dropMenu) {
  85. style =
  86. dropMenu.props.direction === 'down'
  87. ? `top: calc(var(--window-top) + ${dropMenu.offset.value}px); bottom: 0;`
  88. : `top: 0; bottom: calc(var(--window-bottom) + ${dropMenu.offset.value}px)`
  89. } else {
  90. style = ''
  91. }
  92. return style
  93. })
  94. watch(
  95. () => props.modelValue,
  96. (newValue) => {
  97. if (isDef(newValue) && typeof newValue !== 'number' && typeof newValue !== 'string') {
  98. console.error('[wot-design] warning(wd-drop-menu-item): the type of value should be a number or a string.')
  99. }
  100. },
  101. {
  102. deep: true,
  103. immediate: true
  104. }
  105. )
  106. onBeforeMount(() => {
  107. if (queue && queue.pushToQueue) {
  108. queue.pushToQueue(proxy)
  109. } else {
  110. pushToQueue(proxy)
  111. }
  112. })
  113. onBeforeUnmount(() => {
  114. if (queue && queue.removeFromQueue) {
  115. queue.removeFromQueue(proxy)
  116. } else {
  117. removeFromQueue(proxy)
  118. }
  119. })
  120. function getShowPop() {
  121. return showPop.value
  122. }
  123. // 模拟单选操作 默认根据 value 选中操作
  124. function choose(index: number) {
  125. if (props.disabled) return
  126. const { valueKey } = props
  127. const item = props.options[index]
  128. const newValue = item[valueKey] !== undefined ? item[valueKey] : item
  129. emit('update:modelValue', newValue)
  130. emit('change', {
  131. value: newValue,
  132. selectedItem: item
  133. })
  134. close()
  135. }
  136. // 外部关闭弹出框
  137. function close() {
  138. if (!showPop.value) {
  139. return
  140. }
  141. if (isFunction(props.beforeToggle)) {
  142. props.beforeToggle({
  143. status: false,
  144. resolve: (isPass: boolean) => {
  145. isPass && handleClose()
  146. }
  147. })
  148. } else {
  149. handleClose()
  150. }
  151. }
  152. function handleClose() {
  153. if (showPop.value) {
  154. showPop.value = false
  155. }
  156. }
  157. function open() {
  158. if (showPop.value) {
  159. return
  160. }
  161. if (isFunction(props.beforeToggle)) {
  162. props.beforeToggle({
  163. status: true,
  164. resolve: (isPass) => {
  165. isPass && handleOpen()
  166. }
  167. })
  168. } else {
  169. handleOpen()
  170. }
  171. }
  172. function handleOpen() {
  173. showWrapper.value = true
  174. showPop.value = true
  175. if (dropMenu) {
  176. duration.value = Number(dropMenu.props.duration)
  177. position.value = dropMenu.props.direction === 'down' ? 'top' : 'bottom'
  178. }
  179. }
  180. function toggle() {
  181. if (showPop.value) {
  182. close()
  183. } else {
  184. open()
  185. }
  186. }
  187. function afterLeave() {
  188. showWrapper.value = false
  189. emit('closed')
  190. }
  191. function beforeEnter() {
  192. emit('open')
  193. }
  194. function afterEnter() {
  195. emit('opened')
  196. }
  197. function beforeLeave() {
  198. emit('close')
  199. }
  200. defineExpose<DropMenuItemExpose>({ getShowPop, open, close, toggle })
  201. </script>
  202. <style lang="scss" scoped>
  203. @import './index.scss';
  204. </style>