携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第27天,点击查看活动详情
后台项目前端综合解决方案之通用功能开发
数据源重处理,生成 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
}
在 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>
通过 querySearch 测试搜索结果
// 搜索方法
const querySearch = query => {
console.log(fuse.search(query))
}
渲染搜索数据
数据源处理完之后,我们就需要完成渲染搜索出的数据以及对应的跳转
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>
2、完成对应的跳转
// 选中回调
const onSelectChange = val => {
router.push(val.path)
}
剩余问题处理
到这里我们的 headerSearch 功能基本上已经完成了,但是还存在一些小 bug,那么下边我们就来一一处理
1、在 search 打开时,点击 body 关闭 search
2、在 search 关闭时,清理 searchOptions
3、headerSearch 应具备国际化的能力
我们首先处理前两个问题
/**
* 关闭 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)
}
})
接下来处理国际化的问题,想要处理这个问题非常简单,我们只需要监听语言变化重新计算数据源初始化 fuse 即可
在 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))
}
)
}
在 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>
headerSearch 方案总结
到这里整个 headerSearch 我们已经处理完毕了,整个 headerSearch 我们只需要把握三个核心的关键点
1、根据指定内容对所有页面进行检索
2、以 select 形式展示检索出对应的页面
3、通过检索页面可快速进入对应页面
而比较复杂的点可能有两个地方
1、模糊搜索
2、检索数据源
对于这两块,我们依赖于 fuse.js 进行了实现,简化了我们的业务处理