Blog
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)

HiuZing

🍑
首页
文档
收藏
关于
  • 在线转换时间戳 (opens new window)
  • 在线压缩图片 (opens new window)
  • Float-Double转二进制 (opens new window)
  • 文件转Hex字符串 (opens new window)
  • 前端面试题

  • JavaScript

  • Vue2

  • port

  • CSS

  • Node.js

  • JavaScript优化

  • uniapp

  • Mini Program

  • TypeScript

  • 面向对象编程

  • UI组件

  • Plugin

  • Vue3

    • 教程

    • Vue Router

    • API

    • Vuex

    • 实例处理方案

      • 图标处理方案
      • 本地缓存处理方案
      • 响应拦截器处理方案
      • 登录鉴权处理方案
      • 退出登录处理方案
      • 国际化处理方案
      • 动态换肤处理方案
      • Screenfull全屏处理方案
      • HeaderSearch 处理方案
        • HeaderSearch 原理及方案分析
          • 原理:
          • 方案:
        • 方案落地:创建 headerSearch 组件
        • 方案落地:对检索数据源进行模糊搜索
        • 方案落地:数据源重处理,生成 searchPool
        • 方案落地:渲染检索数据
        • 方案落地:剩余问题处理
        • headerSearch 方案总结
      • TagsView处理方案
      • Guide 处理方案
      • Excel 导入处理方案
      • 打印详情处理方案
      • 权限受控处理方案
      • 动态表格处理方案
      • 富文本和markdown处理方案
      • 项目部署处理方案
      • 可视化处理方案
    • 文档

    • 用法

  • 性能优化

  • Axios

  • 状态管理

  • React

  • Mock

  • Icon

  • Template

  • 构建工具

  • 项目规范配置

  • Taro

  • SVG

  • React Native

  • 前端
  • Vue3
  • 实例处理方案
HiuZing
2023-03-18
目录

HeaderSearch 处理方案

# HeaderSearch 原理及方案分析

所谓 headerSearch 指 页面搜索

# 原理:

headerSearch 是复杂后台系统中非常常见的一个功能,它可以:在指定搜索框中对当前应用中所有页面进行检索,以 select 的形式展示出被检索的页面,以达到快速进入的目的

功能点:

  1. 根据指定内容对所有页面进行检索
  2. 以 select 形式展示检索出的页面
  3. 通过检索页面可快速进入对应页面

根据指定内容检索所有页面,把检索出的页面以 select 展示,点击对应 option 可进入

# 方案:

  1. 创建 headerSearch 组件,用作样式展示和用户输入内容获取

  2. 获取所有的页面数据,用作被检索的数据源

  3. 根据用户输入内容在数据源中进行 模糊搜索 (opens new window)

  4. 把搜索到的内容以 select 进行展示

  5. 监听 select 的 change 事件,完成对应跳转

# 方案落地:创建 headerSearch 组件

创建components/headerSearch/index` 组件:

<template>
  <div :class="{ show: isShow }" class="header-search">
    <svg-icon
      class-name="search-icon"
      icon="search"
      @click.stop="onShowClick"
    />
    <el-select
      ref="headerSearchSelectRef"
      class="header-search-select"
      v-model="search"
      filterable
      default-first-option
      remote
      placeholder="Search"
      :remote-method="querySearch"
      @change="onSelectChange"
    >
      <el-option
        v-for="option in 5"
        :key="option"
        :label="option"
        :value="option"
      ></el-option>
    </el-select>
  </div>
</template>

<script setup>
import { ref } from 'vue'

// 控制 search 显示
const isShow = ref(false)
// el-select 实例
const headerSearchSelectRef = ref(null)
const onShowClick = () => {
  isShow.value = !isShow.value
  headerSearchSelectRef.value.focus()
}

// search 相关
const search = ref('')
// 搜索方法
const querySearch = () => {
  console.log('querySearch')
}
// 选中回调
const onSelectChange = () => {
  console.log('onSelectChange')
}
</script>

<style lang="scss" scoped>
.header-search {
  font-size: 0 !important;
  .search-icon {
    cursor: pointer;
    font-size: 18px;
    vertical-align: middle;
  }
  .header-search-select {
    font-size: 18px;
    transition: width 0.2s;
    width: 0;
    overflow: hidden;
    background: transparent;
    border-radius: 0;
    display: inline-block;
    vertical-align: middle;

    ::v-deep .el-input__inner {
      border-radius: 0;
      border: 0;
      padding-left: 0;
      padding-right: 0;
      box-shadow: none !important;
      border-bottom: 1px solid #d9d9d9;
      vertical-align: middle;
    }
  }
  &.show {
    .header-search-select {
      width: 210px;
      margin-left: 10px;
    }
  }
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88

在 navbar 中导入该组件

<header-search class="right-menu-item hover-effect"></header-search>
import HeaderSearch from '@/components/HeaderSearch'
1
2

接下来处理对应的 检索数据源

那么对于我们当前的业务而言,我们希望被检索的页面其实就是左侧菜单中的页面,那么我们检索数据源即为:左侧菜单对应的数据源

<script setup>
import { ref, computed } from 'vue'
import { filterRouters, generateMenus } from '@/utils/route'
import { useRouter } from 'vue-router'
...
// 检索数据源
const router = useRouter()
const searchPool = computed(() => {
  const filterRoutes = filterRouters(router.getRoutes())
  console.log(generateMenus(filterRoutes))
  return generateMenus(filterRoutes)
})
console.log(searchPool)
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 方案落地:对检索数据源进行模糊搜索

如果我们想要进行 模糊搜索 (opens new window) 的话,那么需要依赖一个第三方的库 fuse.js (opens new window)

  1. 安装 fuse.js (opens new window)

    npm install --save [email protected]
    
    1
  2. 初始化 Fuse,更多初始化配置项 可点击这里 (opens new window)

    import Fuse from 'fuse.js'
    
    /**
     * 搜索库相关
     */
    const fuse = new Fuse(list, {
        // 是否按优先级进行排序
        shouldSort: true,
        // 匹配长度超过这个值的才会被认为是匹配的
        minMatchCharLength: 1,
        // 将被搜索的键列表。 这支持嵌套路径、加权搜索、在字符串和对象数组中搜索。
        // name:搜索的键
        // weight:对应的权重
        keys: [
          {
            name: 'title',
            weight: 0.7
          },
          {
            name: 'path',
            weight: 0.3
          }
        ]
      })
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
  3. 参考 Fuse Demo (opens new window) 与 最终效果,可以得出,我们最终期望得到如下的检索数据源结构

    [
        {
            "path":"/my",
            "title":[
                "个人中心"
            ]
        },
        {
            "path":"/user",
            "title":[
                "用户"
            ]
        },
        {
            "path":"/user/manage",
            "title":[
                "用户",
                "用户管理"
            ]
        },
        {
            "path":"/user/info",
            "title":[
                "用户",
                "用户信息"
            ]
        },
        {
            "path":"/article",
            "title":[
                "文章"
            ]
        },
        {
            "path":"/article/ranking",
            "title":[
                "文章",
                "文章排名"
            ]
        },
        {
            "path":"/article/create",
            "title":[
                "文章",
                "创建文章"
            ]
        }
    ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
  4. 所以我们之前处理了的数据源并不符合我们的需要,所以我们需要对数据源进行重新处理

# 方案落地:数据源重处理,生成 searchPool

我们明确了最终我们期望得到数据源结构,那么接下来我们就对重新计算数据源,生成对应的 searchPoll

创建 compositions/HeaderSearch/FuseData.js

import path from 'path'
import i18n from '@/i18n'
/**
 * 筛选出可供搜索的路由对象
 * @param routes 路由表
 * @param basePath 基础路径,默认为 /
 * @param prefixTitle
 */
export const generateRoutes = (routes, basePath = '/', prefixTitle = []) => {
  // 创建 result 数据
  let res = []
  // 循环 routes 路由
  for (const route of routes) {
    // 创建包含 path 和 title 的 item
    const data = {
      path: path.resolve(basePath, route.path),
      title: [...prefixTitle]
    }
    // 当前存在 meta 时,使用 i18n 解析国际化数据,组合成新的 title 内容
    // 动态路由不允许被搜索
    // 匹配动态路由的正则
    const re = /.*\/:.*/
    if (route.meta && route.meta.title && !re.exec(route.path)) {
      const i18ntitle = i18n.global.t(`msg.route.${route.meta.title}`)
      data.title = [...data.title, i18ntitle]
      res.push(data)
    }

    // 存在 children 时,迭代调用
    if (route.children) {
      const tempRoutes = generateRoutes(route.children, data.path, data.title)
      if (tempRoutes.length >= 1) {
        res = [...res, ...tempRoutes]
      }
    }
  }
  return res
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

在 headerSearch 中导入 generateRoutes

<script setup>
import { computed, ref } from 'vue'
import { generateRoutes } from './FuseData'
import Fuse from 'fuse.js'
import { filterRouters } from '@/utils/route'
import { useRouter } from 'vue-router'

...

// 检索数据源
const router = useRouter()
const searchPool = computed(() => {
  const filterRoutes = filterRouters(router.getRoutes())
  return generateRoutes(filterRoutes)
})
/**
 * 搜索库相关
 */
const fuse = new Fuse(searchPool.value, {
  ...
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

通过 querySearch 测试搜索结果

// 搜索方法
const querySearch = query => {
  console.log(fuse.search(query))
}
1
2
3
4

# 方案落地:渲染检索数据

  1. 渲染检索出的数据

    <template>
      <el-option
          v-for="option in searchOptions"
          :key="option.item.path"
          :label="option.item.title.join(' > ')"
          :value="option.item"
      ></el-option>
    </template>
    
    <script setup>
    ...
    // 搜索结果
    const searchOptions = ref([])
    // 搜索方法
    const querySearch = query => {
      if (query !== '') {
        searchOptions.value = fuse.search(query)
      } else {
        searchOptions.value = []
      }
    }
    ...
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
  2. 完成对应跳转

    // 选中回调
    const onSelectChange = val => {
      router.push(val.path)
    }
    
    1
    2
    3
    4

# 方案落地:剩余问题处理

到这里我们的 headerSearch 功能基本上就已经处理完成了,但是还存在一些小 bug ,那么最后这一小节我们就处理下这些剩余的 bug

  1. 在 search 打开时,点击 body 关闭 search
  2. 在 search 关闭时,清理 searchOptions
  3. headerSearch 应该具备国际化能力

search两个问题:

/**
 * 关闭 search 的处理事件
 */
const onClose = () => {
  headerSearchSelectRef.value.blur()
  isShow.value = false
  searchOptions.value = []
}
/**
 * 监听 search 打开,处理 close 事件
 */
watch(isShow, val => {
  if (val) {
    document.body.addEventListener('click', onClose)
  } else {
    document.body.removeEventListener('click', onClose)
  }
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

国际化的问题:

只需要:监听语言变化,重新计算数据源初始化 fuse 即可

  1. 在 utils/i18n 下,新建方法 watchSwitchLang

    import { watch } from 'vue'
    import store from '@/store'
    
    /**
     *
     * @param  {...any} cbs 所有的回调
     */
    export function watchSwitchLang(...cbs) {
      watch(
        () => store.getters.language,
        () => {
          cbs.forEach(cb => cb(store.getters.language))
        }
      )
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
  2. 在 headerSearch 监听变化,重新赋值

    <script setup>
    ...
    import { watchSwitchLang } from '@/utils/i18n'
    
    ...
    
    // 检索数据源
    const router = useRouter()
    let searchPool = computed(() => {
      const filterRoutes = filterRouters(router.getRoutes())
      return generateRoutes(filterRoutes)
    })
    
    /**
     * 搜索库相关
     */
    // 封装起来,方便调用
    let fuse
    const initFuse = searchPool => {
      fuse = new Fuse(searchPool, {
        ...
    }
    initFuse(searchPool.value)
    
    ...
    
    // 处理国际化
    watchSwitchLang(() => {
      searchPool = computed(() => {
        const filterRoutes = filterRouters(router.getRoutes())
        return generateRoutes(filterRoutes)
      })
      initFuse(searchPool.value)
    })
    </script>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35

# headerSearch 方案总结

  1. 根据指定内容对所有页面进行检索
  2. 以 select 形式展示检索出的页面
  3. 通过检索页面可快速进入对应页面

关于细节的处理,可能比较复杂的地方有两个:

  1. 模糊搜索
  2. 检索数据源
上次更新: 2025/06/23, 07:26:12
Screenfull全屏处理方案
TagsView处理方案

← Screenfull全屏处理方案 TagsView处理方案→

最近更新
01
CodePush
06-22
02
打包发布
03-09
03
常用命令
03-09
更多文章>
Theme by Vdoing | Copyright © 2021-2025 WeiXiaojing | 友情链接
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式