敲里个敲敲---领导的定制版搜索框

随便聊两句

哈喽,大家好😁!我是敲里个敲敲,前端领域摸爬滚打者,希望能通过这篇文章一起学习,一起收获。

快下班时候,老板突然叫我过去:

老板:我有一个idea,你这个搜索框可以这样效果,是不是更好,更符合用户使用?辛苦一下搞出来,看好你!

敲里个敲敲:老板,我也有idea,涨薪能激发员工动力,写的更快更好!

真实回答:

卑微小敲:好的领导!

拿钱办事,哎,加班!

UI设计及需求

UI设计如上,不单单需要鼠标滚轮上下滑动,而且需要键盘按钮⬆️⬇️实现上下翻动效果

技术栈:vue3+ts+Ant design vue

下面我将一步步构建出来,Let's Go!

第一步:实现聚焦与失焦

xml 复制代码
<template>
  <div class="container">
    <a-input
      id="input"
      class="search-input"
      v-model:value="searchValue"
      type="text"
      placeholder="Search"
      allow-clear
    >
      <template #prefix> <MonitorOutlined /> </template>
    </a-input>
  </div>
</template>

<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
const searchValue = ref('')
</script>

<style lang="less" scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}
.search-input {
  position: absolute;
  left: 0;
  width: 230px;
}
:deep(.ant-input-affix-wrapper) {
  &-focused {
    width: 268px;
  }
}
</style>

这里就是简单的引入,根据聚焦时候改变输入框宽度,没什么好说的😎😎😎

第二步:聚焦状态搜索展示结果

聚焦展示两种结果:

1、无数据,展示当前没有数据

2、存在数据,展示数据

xml 复制代码
<template>
  <div class="container">
    <a-input
      id="input"
      class="search-input"
      v-model:value="searchValue"
      type="text"
      placeholder="Search"
      allow-clear
      @change="onSearch"
    >
      <template #prefix> <MonitorOutlined /> </template>
    </a-input>
    <div class="search-result">
     <!-- 有数据 -->
      <div v-if="searchResult?.length">
        <div
          class="search-result-item"
          v-for="(item, index) in searchResult"
          :key="index"
          @click="itemSelected(item)"
        >
         <span v-if="item">{{ item }}</span>
        </div>
      </div>
      <!-- 无数据 -->
      <div class="no-result" v-else>
        <span>No search results</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
// 搜索框输入数据
const searchValue = ref('')
//-----------------新增:搜索结果
const searchResult = ref()
//-----------------新增:模拟数据库数据数据
const data = [
  'richard',
  'tom',
  'lucy',
  'jerry',
  'jxxx',
  'yyyj',
  'ajax',
  'exngj',
  'jerry',
  'jxxx',
  'yyyj',
  'ajax',
  'exngj',
  'lily',
  'lucy',
  'michael',
  '张三',
  '李四',
  '王五',
  '赵六',
]

//-----------------新增: 获取搜索结果
const onSearch = () => {
  // 无输入则不搜索
  if (searchValue.value === '') {
    return (searchResult.value = [])
  }
  // 搜索
  searchResult.value = data.filter((item) => item.includes(searchValue.value))
}

//-----------------新增: 选中的item
const itemSelected = (item: string) => {
  console.log(item)
}
</script>

<style lang="less" scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: absolute;
  left: 0;
  color: black;
}
.search-input {
  width: 230px;
}
.search-result {
  display: flex;
  width: 268px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100px;
  max-height: 200px;
  overflow-y: scroll;
  background: #fff;
  border-radius: 12px;
  .no-result {
    width: 100%;
    cursor: pointer;
    text-align: center;
    > span {
      display: block;
      padding: 12px 0;
    }
  }
  .search-result-item {
    width: 100%;
    cursor: pointer;
    text-align: center;
    > span {
      display: block;
      padding: 12px 0;
    }
  }
  .search-result-item:hover {
    background: #ccc;
  }
}
:deep(.ant-input-affix-wrapper) {
  &-focused {
    width: 268px;
  }
}
::-webkit-scrollbar {
  display: none;
}
</style>

解释:

  1. 将模拟数据作为数据库数据,然后根据用户输入去过滤符合的数据(防抖节流等优化各位自己加吧,敲子我就偷个懒了🤣🤣🤣)
  2. 根据匹配数据,展示结果

第三步:搜索结果动态展现与消失

聪明的你一定发现了下面这个问题:

对!结果框一直存在并且不关闭,接下来我们关闭一下他 HTML中增加: @blur="blurFn" @focus="focusFn"来控制聚焦失焦,我们只要做到聚焦展示失焦消失即可。 完整代码如下:

xml 复制代码
<template>
  <div class="container">
    <a-input
      id="input"
      class="search-input"
      v-model:value="searchValue"
      type="text"
      placeholder="Search"
      allow-clear
      @change="onSearch"
      @blur="blurFn"
      @focus="focusFn"
    >
      <template #prefix> <MonitorOutlined /> </template>
    </a-input>
    <div class="search-result" v-if="isFocus">
       <!-- 有数据 -->
      <div v-if="searchResult?.length">
        <div
          class="search-result-item"
          v-for="(item, index) in searchResult"
          :key="index"
          @click="itemSelected(item)"
        >
         <span v-if="item">{{ item }}</span>
        </div>
      </div>
      <!-- 无数据 -->
      <div class="no-result" v-else>
        <span>No search results</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
// 搜索框输入数据
const searchValue = ref('')
// 搜索结果
const searchResult = ref()

//-----------------新增: 输入框是否获取焦点
const isFocus = ref(false)
// 模拟数据库数据数据
const data = [
  'richard',
  'tom',
  'lucy',
  'jerry',
  'jxxx',
  'yyyj',
  'ajax',
  'exngj',
  'jerry',
  'jxxx',
  'yyyj',
  'ajax',
  'exngj',
  'lily',
  'lucy',
  'michael',
  '张三',
  '李四',
  '王五',
  '赵六',
]

// 获取搜索结果
const onSearch = () => {
  // 无输入则不搜索
  if (searchValue.value === '') {
    return (searchResult.value = [])
  }
  // 搜索
  searchResult.value = data.filter((item) => item.includes(searchValue.value))
}

// 选中的item
const itemSelected = (item: string) => {
  console.log(item)
}
//-----------------新增: 输入框获取焦点
function focusFn() {
  isFocus.value = true
}
//-----------------新增:输入框失焦
function blurFn() {
  isFocus.value = false
}
</script>

<style lang="less" scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: absolute;
  left: 0;
  color: black;
}
.search-input {
  width: 230px;
}
.search-result {
  display: flex;
  width: 268px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100px;
  max-height: 200px;
  overflow-y: scroll;
  background: #fff;
  border-radius: 12px;
  .no-result {
    width: 100%;
    cursor: pointer;
    text-align: center;
    > span {
      display: block;
      padding: 12px 0;
    }
  }
  .search-result-item {
    width: 100%;
    cursor: pointer;
    text-align: center;
    > span {
      display: block;
      padding: 12px 0;
    }
  }
  .search-result-item:hover {
    background: #ccc;
  }
}
:deep(.ant-input-affix-wrapper) {
  &-focused {
    width: 268px;
  }
}
::-webkit-scrollbar {
  display: none;
}
</style>

第三步:实现匹配数据变色

当我们搜索的内容和搜索结果一样时候,匹配到的字段变蓝色,如下:

其实就是html中使用v-html渲染 <span v-html="highlightedText(item)"></span> 然后就是实现highlightedText函数:

javascript 复制代码
function highlightedText(title: string) {
  if (!searchValue.value) {
    return title
  }

  // 创建正则表达式,用于查找并高亮显示匹配的部分
  const regex = new RegExp(`(${searchValue.value})`, 'gi')
  // 使用 replace 将匹配部分包裹在 <span> 中,并设置颜色
  return title.replace(regex, '<span style="color: #1800D3;">$1</span>')
}

完整代码如下:

xml 复制代码
<template>
  <div class="container">
    <a-input
      id="input"
      class="search-input"
      v-model:value="searchValue"
      type="text"
      placeholder="Search"
      allow-clear
      @change="onSearch"
      @blur="blurFn"
      @focus="focusFn"
    >
      <template #prefix> <MonitorOutlined /> </template>
    </a-input>
    <div class="search-result" v-if="isFocus">
      <!-- 有数据 -->
      <div v-if="searchResult?.length">
        <div
          class="search-result-item"
          v-for="(item, index) in searchResult"
          :key="index"
          @click="itemSelected(item)"
        >
          <span v-html="highlightedText(item)"></span>
        </div>
      </div>
      <!-- 无数据 -->
      <div class="no-result" v-else>
        <span>No search results</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
// 搜索框输入数据
const searchValue = ref('')
// 搜索结果
const searchResult = ref()

// 输入框是否获取焦点
const isFocus = ref(false)
// 模拟数据库数据数据
const data = [
  'richard',
  'tom',
  'lucy',
  'jerry',
  'jxxx',
  'yyyj',
  'ajax',
  'exngj',
  'jerry',
  'jxxx',
  'yyyj',
  'ajax',
  'exngj',
  'lily',
  'lucy',
  'michael',
  '张三',
  '李四',
  '王五',
  '赵六',
]

// 获取搜索结果
const onSearch = () => {
  // 无输入则不搜索
  if (searchValue.value === '') {
    return (searchResult.value = [])
  }
  // 搜索
  searchResult.value = data.filter((item) => item.includes(searchValue.value))
}

// 选中的item
const itemSelected = (item: string) => {
  console.log(item)
}

// 输入框获取焦点
function focusFn() {
  isFocus.value = true
}
// 输入框失焦
function blurFn() {
  isFocus.value = false
}

//-----------------新增:高亮显示匹配的文本
function highlightedText(title: string) {
  if (!searchValue.value) {
    return title
  }

  // 创建正则表达式,用于查找并高亮显示匹配的部分
  const regex = new RegExp(`(${searchValue.value})`, 'gi')
  // 使用 replace 将匹配部分包裹在 <span> 中,并设置颜色
  return title.replace(regex, '<span style="color: #1800D3;">$1</span>')
}
</script>

<style lang="less" scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: absolute;
  left: 0;
  color: black;
}
.search-input {
  width: 230px;
}
.search-result {
  display: flex;
  width: 268px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100px;
  max-height: 200px;
  overflow-y: scroll;
  background: #fff;
  border-radius: 12px;
  .no-result {
    width: 100%;
    cursor: pointer;
    text-align: center;
    > span {
      display: block;
      padding: 12px 0;
    }
  }
  .search-result-item {
    width: 100%;
    cursor: pointer;
    text-align: center;
    > span {
      display: block;
      padding: 12px 0;
    }
  }
  .search-result-item:hover {
    background: #ccc;
  }
}
:deep(.ant-input-affix-wrapper) {
  &-focused {
    width: 268px;
  }
}
::-webkit-scrollbar {
  display: none;
}
</style>

第四步:键盘⬆️⬇️翻滚,Enter选中

首先,肯定需要增加一个监听键盘的事件: @keydown="handleKeydown"

然后,需要定义一个变量,判断这个变量跟选中的option的index,然后展示颜色动态加载颜色即可:

const defaultSelect = ref(0)

:class="defaultSelect === index ? 'search-options-item-selected' : ''"

完整代码如下:

xml 复制代码
<template>
  <div class="container">
    <a-input
      id="input"
      class="search-input"
      v-model:value="searchValue"
      type="text"
      placeholder="Search"
      allow-clear
      @change="onSearch"
      @blur="blurFn"
      @focus="focusFn"
      @keydown="handleKeydown"
    >
      <template #prefix> <MonitorOutlined /> </template>
    </a-input>
    <div class="search-result" v-if="isFocus">
      <!-- 有数据 -->
      <div v-if="searchResult?.length">
        <div class="search-result-item" v-for="(item, index) in searchResult" :key="index">
          <span
            @click="itemSelected(item)"
            v-html="highlightedText(item)"
            :class="defaultSelect === index ? 'search-options-item-selected' : ''"
          ></span>
        </div>
      </div>
      <!-- 无数据 -->
      <div class="no-result" v-else>
        <span>No search results</span>
      </div>
    </div>
  </div>
</template>

<script setup lang="ts">
import { MonitorOutlined } from '@ant-design/icons-vue'
import { ref } from 'vue'
// 搜索框输入数据
const searchValue = ref('')
// 搜索结果
const searchResult = ref()

// 输入框是否获取焦点
const isFocus = ref(false)
// 模拟数据库数据数据
const data = [
  'richard',
  'tom',
  'lucy',
  'jerry',
  'jxxx',
  'yyyj',
  'ajax',
  'exngj',
  'jerry',
  'lily',
  'lucy',
  'michael',
  '张三',
  '李四',
  '王五',
  '赵六',
]

// 获取搜索结果
const onSearch = () => {
  // 无输入则不搜索
  if (searchValue.value === '') {
    return (searchResult.value = [])
  }
  // 搜索
  searchResult.value = data.filter((item) => item.includes(searchValue.value))
}

// 选中的item
const itemSelected = (item: string) => {
  console.log(item)
}

// 输入框获取焦点
function focusFn() {
//-----------------新增: 重置默认选中option
  defaultSelect.value = 0
  isFocus.value = true
}
// 输入框失焦
function blurFn() {
  isFocus.value = false
}

// 高亮显示匹配的文本
function highlightedText(title: string) {
  if (!searchValue.value) {
    return title
  }

  // 创建正则表达式,用于查找并高亮显示匹配的部分
  const regex = new RegExp(`(${searchValue.value})`, 'gi')
  // 使用 replace 将匹配部分包裹在 <span> 中,并设置颜色
  return title.replace(regex, '<span style="color: #1800D3;">$1</span>')
}

//-----------------新增: 键盘事件处理
const defaultSelect = ref(0)
const handleKeydown = (event: { key: any }) => {
  switch (event.key) {
    case 'ArrowUp':
      if (defaultSelect.value > 0) {
        defaultSelect.value--
        const selectedItem = document.querySelector('.search-options-item-selected') as HTMLElement
        selectedItem.scrollIntoView({ behavior: 'smooth', block: 'center' })
      }
      break
    case 'ArrowDown':
      if (defaultSelect.value < searchResult.value.length - 1) {
        defaultSelect.value++
        const selectedItem = document.querySelector('.search-options-item-selected') as HTMLElement
        selectedItem.scrollIntoView({ behavior: 'smooth', block: 'center' })
      }
      break
    case 'Enter':
      itemSelected(searchResult.value[defaultSelect.value])
      ;(document.querySelector('#input') as HTMLInputElement)?.blur()
      break
    default:
      break
  }
}
</script>

<style lang="less" scoped>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: absolute;
  left: 0;
  color: black;
}
.search-input {
  width: 230px;
}
.search-result {
  display: flex;
  width: 268px;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  min-height: 100px;
  max-height: 200px;
  overflow-y: scroll;
  background: #fff;
  border-radius: 12px;

  .no-result {
    width: 100%;
    cursor: pointer;
    text-align: center;
    > span {
      display: block;
      padding: 12px 0;
    }
  }
  .search-result-item {
    width: 100%;
    cursor: pointer;
    text-align: center;

    > span {
      display: inline-block;
      padding: 12px 0;
      width: 100%;
    }
  }
  .search-result-item:hover {
    background: #ccc;
  }
}
.search-options-item-selected {
  background: #ccc;
}
:deep(.ant-input-affix-wrapper) {
  &-focused {
    width: 268px;
  }
}
::-webkit-scrollbar {
  display: none;
}
</style>

注意:有个小坑

更加聪明的你又会发现,鼠标点击不起作用呀,选不到所需的option😢😢😢。

对喽,我们需要延迟关闭options从而给用户选中,在blurFn里面写如下代码:

javascript 复制代码
// 输入框失焦
function blurFn() {
  // 失焦延迟关闭,不然不会触发 onSelect
  setTimeout(() => {
    isFocus.value = false
  }, 200)
}

小结

终于做完了,老板说做的不错,只要好好干以后肯定给我升职加薪。这把稳了!兄弟们学起来🏄🏄‍♂️🏄‍♀️

关注敲里个敲敲带你领略不一样的风采!

相关推荐
2401_882727576 分钟前
BY组态-低代码web可视化组件
前端·后端·物联网·低代码·数学建模·前端框架
会发光的猪。41 分钟前
css使用弹性盒,让每个子元素平均等分父元素的4/1大小
前端·javascript·vue.js
天下代码客1 小时前
【vue】vue中.sync修饰符如何使用--详细代码对比
前端·javascript·vue.js
猫爪笔记1 小时前
前端:HTML (学习笔记)【1】
前端·笔记·学习·html
前端李易安1 小时前
Webpack 热更新(HMR)详解:原理与实现
前端·webpack·node.js
红绿鲤鱼1 小时前
React-自定义Hook与逻辑共享
前端·react.js·前端框架
Domain-zhuo2 小时前
什么是JavaScript原型链?
开发语言·前端·javascript·jvm·ecmascript·原型模式
小丁爱养花2 小时前
前端三剑客(三):JavaScript
开发语言·前端·javascript
ZwaterZ2 小时前
vue el-table表格点击某行触发事件&&操作栏点击和row-click冲突问题
前端·vue.js·elementui·c#·vue
西凉河的葛三叔2 小时前
vue3+elementui-plus el-dialog全局配置点击空白处不关闭弹窗
前端·vue3·elementui-plus