wd-search.vue 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. <template>
  2. <view :class="rootClass" :style="customStyle">
  3. <view class="wd-search__block">
  4. <slot name="prefix"></slot>
  5. <view class="wd-search__field">
  6. <view v-if="!placeholderLeft" :style="coverStyle" class="wd-search__cover" @click="closeCover">
  7. <wd-icon name="search" custom-class="wd-search__search-icon"></wd-icon>
  8. <text :class="`wd-search__placeholder-txt ${placeholderClass}`">{{ placeholder || translate('search') }}</text>
  9. </view>
  10. <wd-icon v-if="showInput || inputValue || placeholderLeft" name="search" custom-class="wd-search__search-left-icon"></wd-icon>
  11. <input
  12. v-if="showInput || inputValue || placeholderLeft"
  13. :placeholder="placeholder || translate('search')"
  14. :placeholder-class="`wd-search__placeholder-txt ${placeholderClass}`"
  15. :placeholder-style="placeholderStyle"
  16. confirm-type="search"
  17. v-model="inputValue"
  18. :class="['wd-search__input', customInputClass]"
  19. @focus="handleFocus"
  20. @input="handleInput"
  21. @blur="handleBlur"
  22. @confirm="handleConfirm"
  23. :disabled="disabled"
  24. :maxlength="maxlength"
  25. :focus="isFocused"
  26. />
  27. <wd-icon v-if="inputValue" custom-class="wd-search__clear wd-search__clear-icon" name="error-fill" @click="handleClear" />
  28. </view>
  29. </view>
  30. <slot v-if="!hideCancel" name="suffix">
  31. <view class="wd-search__cancel" @click="handleCancel">
  32. {{ cancelTxt || translate('cancel') }}
  33. </view>
  34. </slot>
  35. </view>
  36. </template>
  37. <script lang="ts">
  38. export default {
  39. name: 'wd-search',
  40. options: {
  41. virtualHost: true,
  42. addGlobalClass: true,
  43. styleIsolation: 'shared'
  44. }
  45. }
  46. </script>
  47. <script lang="ts" setup>
  48. import wdIcon from '../wd-icon/wd-icon.vue'
  49. import { type CSSProperties, computed, onMounted, ref, watch } from 'vue'
  50. import { objToStyle, pause } from '../common/util'
  51. import { useTranslate } from '../composables/useTranslate'
  52. import { searchProps } from './types'
  53. const props = defineProps(searchProps)
  54. const emit = defineEmits(['update:modelValue', 'change', 'clear', 'search', 'focus', 'blur', 'cancel'])
  55. const { translate } = useTranslate('search')
  56. const isFocused = ref<boolean>(false) // 是否聚焦中
  57. const showInput = ref<boolean>(false) // 是否显示输入框 用于实现聚焦的hack
  58. const inputValue = ref<string>('') // 输入框的值
  59. const showPlaceHolder = ref<boolean>(true)
  60. const clearing = ref<boolean>(false)
  61. watch(
  62. () => props.modelValue,
  63. (newValue) => {
  64. inputValue.value = newValue
  65. if (newValue) {
  66. showInput.value = true
  67. }
  68. },
  69. { immediate: true }
  70. )
  71. watch(
  72. () => props.focus,
  73. (newValue) => {
  74. if (newValue) {
  75. if (props.disabled) return
  76. closeCover()
  77. }
  78. }
  79. )
  80. onMounted(() => {
  81. if (props.focus) {
  82. closeCover()
  83. }
  84. })
  85. const rootClass = computed(() => {
  86. return `wd-search ${props.light ? 'is-light' : ''} ${props.hideCancel ? 'is-without-cancel' : ''} ${props.customClass}`
  87. })
  88. const coverStyle = computed(() => {
  89. const coverStyle: CSSProperties = {
  90. display: inputValue.value === '' && showPlaceHolder.value ? 'flex' : 'none'
  91. }
  92. return objToStyle(coverStyle)
  93. })
  94. async function hackFocus(focus: boolean) {
  95. showInput.value = focus
  96. await pause()
  97. isFocused.value = focus
  98. }
  99. async function closeCover() {
  100. if (props.disabled) return
  101. await pause(100)
  102. showPlaceHolder.value = false
  103. hackFocus(true)
  104. }
  105. function handleInput(event: any) {
  106. inputValue.value = event.detail.value
  107. emit('update:modelValue', event.detail.value)
  108. emit('change', {
  109. value: event.detail.value
  110. })
  111. }
  112. async function handleClear() {
  113. inputValue.value = ''
  114. if (props.focusWhenClear) {
  115. clearing.value = true
  116. isFocused.value = false
  117. }
  118. await pause()
  119. if (props.focusWhenClear) {
  120. showPlaceHolder.value = false
  121. hackFocus(true)
  122. } else {
  123. showPlaceHolder.value = true
  124. hackFocus(false)
  125. }
  126. emit('change', {
  127. value: ''
  128. })
  129. emit('update:modelValue', '')
  130. emit('clear')
  131. }
  132. function handleConfirm({ detail: { value } }: any) {
  133. // 组件触发search事件
  134. emit('search', {
  135. value
  136. })
  137. }
  138. function handleFocus() {
  139. showPlaceHolder.value = false
  140. emit('focus', {
  141. value: inputValue.value
  142. })
  143. }
  144. async function handleBlur() {
  145. // 等待150毫秒,clear执行完毕
  146. await pause(150)
  147. if (clearing.value) {
  148. clearing.value = false
  149. return
  150. }
  151. // 组件触发blur事件
  152. showPlaceHolder.value = !inputValue.value
  153. showInput.value = !showPlaceHolder.value
  154. isFocused.value = false
  155. emit('blur', {
  156. value: inputValue.value
  157. })
  158. }
  159. function handleCancel() {
  160. emit('cancel', {
  161. value: inputValue.value
  162. })
  163. }
  164. </script>
  165. <style lang="scss" scoped>
  166. @import './index.scss';
  167. </style>