cwg-more-select.vue 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. <template>
  2. <view>
  3. <u-popup closeOnClickOverlay @close="handleClose" v-model="show" :show="show" :round="10" mode="bottom"
  4. border-radius="20">
  5. <view class="currency-mask">
  6. <view class="search" v-if="showSearch && props.options.length > 10">
  7. <up-input class="form-input" v-model="inputValueDoc" type="text" :placeholder="t('common.input')"
  8. :clearable="true" :border="'surround'">
  9. <template #prefix>
  10. <up-icon name="search" size="23"></up-icon>
  11. </template>
  12. </up-input>
  13. </view>
  14. <scroll-view class="currency-select" :style="actionSheetStyle1" scroll-y="true">
  15. <template v-if="filteredOptions.length > 0">
  16. <view v-for="(item, index) in filteredOptions" class="currency-item"
  17. :class="{ selected: item.text === inputValue }" @click="select(item)"
  18. :key="item.value + index" :ref="item.text === inputValue ? 'selectedItem' : null">
  19. {{ item.text }}
  20. </view>
  21. </template>
  22. </scroll-view>
  23. <view currency-select>
  24. <view v-if="filteredOptions.length == 0" class="currency-item">
  25. <view class="empty">
  26. <text>{{ t('Documentary.tradingCenter.item131') }}</text>
  27. </view>
  28. </view>
  29. </view>
  30. </view>
  31. </u-popup>
  32. </view>
  33. </template>
  34. <script setup lang="ts">
  35. import { ref, computed, watch, nextTick } from 'vue'
  36. import { useI18n } from 'vue-i18n'
  37. const { t } = useI18n()
  38. const props = defineProps<{
  39. modelValue: boolean
  40. showSearch: boolean
  41. inputValue: string
  42. options: Array<{ text: string; value: string }>
  43. }>()
  44. const emit = defineEmits(['update:modelValue', 'select'])
  45. const inputValueDoc = ref('')
  46. const show = ref(props.modelValue)
  47. const selectedItem = ref()
  48. const filteredOptions = computed(() => {
  49. if (!inputValueDoc.value) return props.options
  50. const keyword = inputValueDoc.value.toLowerCase()
  51. return props.options.filter((item) => item.text.toLowerCase().includes(keyword))
  52. })
  53. watch(
  54. () => props.modelValue,
  55. (val) => (show.value = val),
  56. )
  57. watch(show, async (val) => {
  58. emit('update:modelValue', val)
  59. inputValueDoc.value = ''
  60. if (val) {
  61. await nextTick()
  62. setTimeout(() => {
  63. const el = Array.isArray(selectedItem.value) ? selectedItem.value[0] : selectedItem.value
  64. }, 300)
  65. }
  66. })
  67. function handleClose() {
  68. emit('update:modelValue', false)
  69. }
  70. const ITEM_HEIGHT = 60
  71. const MAX_VISIBLE_COUNT = 10
  72. const SAFE_OFFSET = 98
  73. const MAX_RATIO = 0.8
  74. const actionSheetStyle1 = computed(() => {
  75. const options = props.options || []
  76. const count = options.length
  77. // ✅ 跨端安全获取高度
  78. const systemInfo = uni.getSystemInfoSync()
  79. const windowHeight = systemInfo.windowHeight || systemInfo.screenHeight
  80. // 超过阈值,固定最大高度
  81. if (count > MAX_VISIBLE_COUNT) {
  82. return {
  83. maxHeight: `${windowHeight * MAX_RATIO - SAFE_OFFSET}px`
  84. }
  85. }
  86. // 根据内容高度计算
  87. const contentHeight = count * ITEM_HEIGHT
  88. const maxHeight = Math.min(
  89. contentHeight,
  90. windowHeight * MAX_RATIO
  91. )
  92. return {
  93. maxHeight: `${maxHeight}px`
  94. }
  95. })
  96. function select(item: { text: string; value: string }) {
  97. emit('select', item)
  98. show.value = false
  99. }
  100. </script>
  101. <style scoped lang="scss">
  102. @import "@/uni.scss";
  103. .currency-mask {
  104. padding: px2rpx(24) px2rpx(16) 0 px2rpx(16);
  105. }
  106. .search {
  107. color: #8e8a8a;
  108. font-family: Roboto;
  109. font-size: px2rpx(16);
  110. font-style: normal;
  111. font-weight: 400;
  112. line-height: px2rpx(24);
  113. letter-spacing: px2rpx(0.08);
  114. display: flex;
  115. justify-content: space-between;
  116. align-items: center;
  117. display: flex;
  118. align-items: center;
  119. gap: px2rpx(82);
  120. border-radius: 30px;
  121. margin-bottom: px2rpx(10);
  122. }
  123. .form-input {
  124. padding: 0;
  125. }
  126. .form-input :deep(.up-input) {
  127. border: none !important;
  128. padding: 0 !important;
  129. }
  130. .form-input :deep(.up-input__content__field-wrapper__field) {
  131. font-size: px2rpx(16) !important;
  132. }
  133. .currency-select {
  134. height: calc(80vh - px2rpx(98));
  135. overflow-y: auto;
  136. padding: 0 0 px2rpx(22) 0;
  137. display: flex;
  138. flex-direction: column;
  139. align-items: center;
  140. z-index: 2;
  141. }
  142. .currency-item {
  143. display: flex;
  144. padding: px2rpx(12) px2rpx(16);
  145. align-items: center;
  146. gap: px2rpx(8);
  147. align-self: stretch;
  148. border-radius: 10px;
  149. color: #0e0f0c;
  150. font-size: px2rpx(16);
  151. font-style: normal;
  152. font-weight: 400;
  153. line-height: px2rpx(24);
  154. letter-spacing: -px2rpx(0.08);
  155. width: 100%;
  156. box-sizing: border-box;
  157. }
  158. .currency-item span {
  159. color: #454745;
  160. font-family: Inter;
  161. font-size: px2rpx(14);
  162. font-style: normal;
  163. font-weight: 400;
  164. line-height: px2rpx(22);
  165. letter-spacing: px2rpx(0.14);
  166. }
  167. .currency-item img {
  168. display: flex;
  169. width: px2rpx(48);
  170. height: px2rpx(48);
  171. justify-content: center;
  172. align-items: center;
  173. aspect-ratio: 1/1;
  174. border-radius: 100px;
  175. border: 1px solid rgba(14, 15, 12, 0.12);
  176. box-shadow: 0px 0px 40px 0px rgba(69, 71, 69, 0.2);
  177. }
  178. .selected {
  179. border-radius: 10px;
  180. background: rgba(255, 209, 216, 0.85);
  181. }
  182. .cancel-btn {
  183. width: 90vw;
  184. padding: px2rpx(20) 0;
  185. text-align: center;
  186. color: #111;
  187. border-radius: 20px;
  188. font-weight: bold;
  189. font-size: px2rpx(22);
  190. box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  191. margin: px2rpx(10) 0;
  192. }
  193. // uView 样式覆盖
  194. :deep(.up-action-sheet__container) {
  195. max-height: 80vh;
  196. }
  197. :deep(.up-input) {
  198. padding: px2rpx(12) px2rpx(16);
  199. }
  200. </style>