ie-dropdown-item.vue 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. <template>
  2. <view class=" h-full">
  3. <view :id="`dropdown-trigger-${config.prop}`" class="w-full flex items-center justify-center gap-10 h-full relative"
  4. @click="handleClick">
  5. <view class="relative">
  6. <view class="text-xs text-center ellipsis-1" :class="[show ? 'text-primary' : 'text-fore-title ']">
  7. {{ config.label }}
  8. </view>
  9. <view class="absolute -top-4 -right-4 w-6 h-6 bg-red-500 rounded-full" v-if="hasValue"></view>
  10. </view>
  11. <view :class="['transition-transform duration-300 ease-out', { 'rotate-180': show }]">
  12. <uv-icon name="arrow-down" :color="show ? 'primary' : 'info'" size="14" />
  13. </view>
  14. </view>
  15. <view v-show="isOpen" class="fixed left-0 right-0 z-[9999]" :style="containerStyle">
  16. <view class="fixed z-0 overflow-hidden" :id="`dropdown-content-${config.prop}`" :style="maskStyle"
  17. @click="handleMaskClick">
  18. </view>
  19. <view class="relative z-1 overflow-hidden ">
  20. <view class="left-0 right-0 z-1 w-full bg-white box-border transition-transform duration-300 ease-out"
  21. :style="contentStyle" @click="">
  22. <scroll-view class="relative max-h-[300px]" scroll-y>
  23. <uv-checkbox-group v-model="checkboxValue" placement="column" iconPlacement="right" borderBottom>
  24. <uv-checkbox :customStyle="{ marginBottom: '0', paddingBottom: '0', height: '38px', padding: '0 10px' }"
  25. v-for="(item, index) in config.options" :key="index" :label="item.label"
  26. :name="item.value"></uv-checkbox>
  27. </uv-checkbox-group>
  28. </scroll-view>
  29. <view class="flex items-center justify-between gap-24 p-[12px]">
  30. <view class="flex-1">
  31. <uv-button type="primary" plain shape="circle" @click="handleReset">重置</uv-button>
  32. </view>
  33. <view class="flex-1">
  34. <uv-button type="primary" shape="circle" @click="handleSubmit">
  35. <text>确定</text>
  36. <text v-if="checkboxValue.length > 0">({{ checkboxValue.length }})</text>
  37. </uv-button>
  38. </view>
  39. </view>
  40. </view>
  41. </view>
  42. </view>
  43. </view>
  44. </template>
  45. <script lang="ts" setup>
  46. import { type Dropdown } from '@/types';
  47. import { DROPDOWN_SYMBOL } from './ie-dropdown-hooks';
  48. import { getCurrentInstance } from 'vue';
  49. const instance = getCurrentInstance();
  50. const props = defineProps<{
  51. config: Dropdown.DropdownItem;
  52. index: number;
  53. // 是否使用绝对定位
  54. absolute?: boolean;
  55. }>();
  56. const dropdown = inject(DROPDOWN_SYMBOL);
  57. const triggerSelector = `#dropdown-trigger-${props.config.prop}`;
  58. const isOpen = ref(false);
  59. const show = ref(false);
  60. const isMeasuringHeight = ref(false);
  61. const top = ref(0);
  62. const hasValue = computed(() => {
  63. return dropdown?.form.value[props.config.prop] && dropdown?.form.value[props.config.prop].length > 0;
  64. });
  65. const checkboxValue = ref([]);
  66. const containerStyle = computed(() => {
  67. return {
  68. top: `${top.value + 1}px`,
  69. }
  70. });
  71. const maskStyle = computed(() => {
  72. return {
  73. top: `${top.value + 1}px`,
  74. left: 0,
  75. right: 0,
  76. bottom: 0,
  77. backgroundColor: 'rgba(0, 0, 0, 0.3)',
  78. opacity: show.value ? 1 : 0,
  79. transition: 'opacity 0.3s ease-out',
  80. }
  81. });
  82. const contentStyle = computed(() => {
  83. return {
  84. transform: `translateY(${show.value ? '0' : '-100%'})`,
  85. }
  86. });
  87. watch(() => dropdown?.openIndex.value, (newVal) => {
  88. if (newVal === props.index) {
  89. open();
  90. } else {
  91. if (isOpen.value) {
  92. close();
  93. }
  94. }
  95. });
  96. const handleReset = () => {
  97. checkboxValue.value = [];
  98. }
  99. const handleSubmit = () => {
  100. dropdown?.submit(props.index, checkboxValue.value);
  101. dropdown?.close();
  102. }
  103. const open = async () => {
  104. checkboxValue.value = dropdown?.form.value[props.config.prop] || [];
  105. isOpen.value = true;
  106. const triggerRect = await getRect(triggerSelector);
  107. if (props.absolute) {
  108. top.value = triggerRect.height;
  109. } else {
  110. top.value = triggerRect.top + triggerRect.height;
  111. }
  112. setTimeout(() => {
  113. nextTick(() => {
  114. show.value = true;
  115. });
  116. }, 20);
  117. }
  118. const close = () => {
  119. show.value = false;
  120. setTimeout(() => {
  121. isOpen.value = false;
  122. }, 300);
  123. }
  124. const getRect = (selector: string) => {
  125. return new Promise((resolve: (rect: { top: number, height: number }) => void) => {
  126. const query = uni.createSelectorQuery().in(instance?.proxy);
  127. query.select(selector).boundingClientRect(function (rect) {
  128. resolve(rect as { top: number, height: number });
  129. }).exec();
  130. });
  131. }
  132. const measureHeight = (selector: string) => {
  133. return new Promise((resolve: (height: number) => void) => {
  134. isMeasuringHeight.value = true;
  135. setTimeout(() => {
  136. nextTick(() => {
  137. getRect(selector).then((res: any) => {
  138. isMeasuringHeight.value = false;
  139. setTimeout(() => {
  140. nextTick(() => {
  141. resolve(res?.height ?? 0);
  142. });
  143. }, 50);
  144. });
  145. });
  146. }, 50);
  147. });
  148. }
  149. const handleMaskClick = () => {
  150. if (isOpen.value) {
  151. dropdown?.close();
  152. }
  153. }
  154. const handleClick = () => {
  155. if (isOpen.value) {
  156. dropdown?.close();
  157. } else {
  158. dropdown?.open(props.index);
  159. }
  160. }
  161. </script>
  162. <style lang="scss" scoped></style>