SearchBar.vue 4.78 KB
<template>
  <view class="search-bar" :class="containerClass">
    <!-- NutUI Input 组件 -->
    <nut-input
      v-model="internalValue"
      :type="inputType"
      :placeholder="placeholder"
      :disabled="disabled"
      confirm-type="search"
      :clearable="showClear"
      :border="false"
      class="search-input"
      @clear="handleClear"
      @blur="handleBlur"
      @focus="handleFocus"
      @confirm="handleSearch"
      style="background: transparent;"
    >
      <template #left>
        <IconFont
          name="search"
          :size="iconSize"
          :color="iconColor"
          class="mr-[8rpx]"
        />
      </template>
    </nut-input>
  </view>
</template>

<script setup>
import { ref, watch, computed } from 'vue'
import IconFont from '@/components/IconFont.vue'

/**
 * SearchBar 组件(基于 NutUI Input)
 *
 * @description 可复用的搜索栏组件,使用 NutUI Input 组件实现
 * @author Claude Code
 * @example
 * <SearchBar
 *   v-model="searchValue"
 *   placeholder="搜索资料..."
 *   variant="rounded"
 *   @search="handleSearch"
 * />
 */

const props = defineProps({
  /**
   * 绑定值
   * @type {string|number}
   */
  modelValue: {
    type: [String, Number],
    default: ''
  },
  /**
   * 占位文本
   * @type {string}
   * @default '搜索...'
   */
  placeholder: {
    type: String,
    default: '搜索...'
  },
  /**
   * 样式变体
   * @type {'normal' | 'rounded'}
   * @default 'normal'
   */
  variant: {
    type: String,
    default: 'normal',
    validator: (value) => ['normal', 'rounded'].includes(value)
  },
  /**
   * 是否显示边框
   * @type {boolean}
   * @default false
   */
  showBorder: {
    type: Boolean,
    default: false
  },
  /**
   * 是否显示清除按钮
   * @type {boolean}
   * @default false
   */
  showClear: {
    type: Boolean,
    default: false
  },
  /**
   * 是否禁用
   * @type {boolean}
   * @default false
   */
  disabled: {
    type: Boolean,
    default: false
  },
  /**
   * 输入框类型
   * @type {string}
   * @default 'text'
   */
  inputType: {
    type: String,
    default: 'text'
  },
  /**
   * 图标大小
   * @type {number|string}
   * @default 18
   */
  iconSize: {
    type: [Number, String],
    default: 18
  },
  /**
   * 图标颜色
   * @type {string}
   * @default '#9CA3AF'
   */
  iconColor: {
    type: String,
    default: '#9CA3AF'
  }
})

const emit = defineEmits({
  /**
   * 更新绑定值
   * @event update:modelValue
   * @param {string|number} value - 新值
   */
  'update:modelValue': (value) => true,
  /**
   * 搜索事件(按下回车)
   * @event search
   * @param {string|number} value - 当前值
   */
  search: (value) => true,
  /**
   * 输入事件
   * @event input
   * @param {string|number} value - 当前值
   */
  input: (value) => true,
  /**
   * 获得焦点
   * @event focus
   */
  focus: () => true,
  /**
   * 失去焦点
   * @event blur
   */
  blur: () => true,
  /**
   * 清除
   * @event clear
   */
  clear: () => true
})

// 内部值
const internalValue = ref(props.modelValue)

// 容器样式类
const containerClass = computed(() => {
  const classes = ['search-bar']

  if (props.variant === 'rounded') {
    classes.push('search-bar-rounded')
  }

  if (props.showBorder) {
    classes.push('search-bar-bordered')
  }

  return classes.join(' ')
})

// 监听 modelValue 变化
watch(() => props.modelValue, (newValue) => {
  internalValue.value = newValue
})

// 监听内部值变化,触发更新
watch(internalValue, (newValue) => {
  emit('update:modelValue', newValue)
  emit('input', newValue)
})

/**
 * 处理获得焦点
 */
function handleFocus() {
  console.log('[SearchBar Component] 获得焦点')
  emit('focus')
}

/**
 * 处理失去焦点
 */
function handleBlur() {
  console.log('[SearchBar Component] 失去焦点')
  emit('blur')
}

/**
 * 处理搜索(回车)
 */
function handleSearch() {
  console.log('[SearchBar Component] handleSearch 被调用')
  console.log('[SearchBar Component] 当前输入值:', internalValue.value)
  emit('search', internalValue.value)
  console.log('[SearchBar Component] search 事件已发送')
}

/**
 * 清除输入
 */
function handleClear() {
  console.log('[SearchBar Component] 清除输入')
  internalValue.value = ''
  emit('clear')
  emit('update:modelValue', '')
}
</script>

<style lang="less">
.search-bar {
  padding: 0;
  background: white;
  box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
  border-radius: 44rpx;
  border: 1px solid #e5e7eb;

  :deep(.nut-input) {
    padding: 24rpx 32rpx;
    background-color: transparent;
  }

  &.search-bar-rounded {
    border-radius: 44rpx;
    overflow: hidden;

    :deep(.nut-input) {
      border-radius: 44rpx;
    }
  }

  &.search-bar-bordered {
    border: 1px solid #e5e7eb;
  }
}

.search-bar-rounded {
  height: 88rpx;
}
</style>