前端本地模糊搜索1.0 按照匹配位置加权

需求背景

公司项目为Saas ERP系统,客户需要快速开单需要避免接口带来的延迟问题。所以需要将商品数据保存在本地。所以本地搜索 + 权重 这一套组合拳需要前端自己实现。

搜索示例
示例1:输入:"男士真皮钱包"进行模糊匹配优先匹配完全同→ 如 【男士真皮钱包】若无结果,展示包含 搜索内容 的商品(OR逻辑)→ 如 【商务男士真皮钱包】→ 如 【商务男士真皮钱包plus版】→ 如 【plus版商务男士真皮钱包】规则:输入:"123"结果:123123xxxxxx123xxx123xxx

解决方案

  • 通过搜索内容对 相关字符串进行切割,以第一个切割位置为准。(例如:"testabc"用"a"分割得到[“test”, “bc”])
  • 针对 ‘test’ 和 ‘bc’ 进行加权重计算 (切割后前面字符串长度 + 1) * 1000 + 切割后后面字符串总长度

技术细节

搜索字段优先级排列
let endResultData = []
// 搜索关键词转为小写字母
const searchLower = this.searchText.toLocaleLowerCase().trim()
// 所有支持搜索的字段,且此处也是搜索的优先级顺序。
let allKeys = ['b', 'c', 'j', 'zjf', 'v', 'w', 'x']
// 储存优先级字段
let allKeyObj = {}
// 构造存储不匹配搜索项key的map
allKeys.map(item => {Reflect.set(allKeyObj, item, {equalArr: [],hasMapKeys: [],})
})
区分完全匹配、部分匹配、完全不匹配数据
let otherItems = []
let resultListMap = new Map()
dataList.map((item, idx) => {const filterText = item.filterText// 匹配策略if (filterText.includes(searchLower)) {let equalKeylet hasKeyallKeys.map(keyIt => {let str = String(item[keyIt]).toLocaleLowerCase()if (str === searchLower) {equalKey = keyIt} else if (str.includes(searchLower)) {hasKey = keyIt}})if (equalKey) {// 完全匹配} else {if (hasKey) {// 部分匹配} else otherItems.push(item) // 不匹配}}
})
权重计算逻辑
  1. 根据关键字使用split 拆分字段
  2. 根据字段拆分结果计算权重 (切割后前面字符串长度 + 1) * 1000 + 切割后后面字符串总长度
  3. 排序整合返回搜索结果数据
/*** 对指定字段进行分割,并计算匹配部分的长度信息(用于搜索结果排序或高亮处理)* @param {Object} data - 原始数据对象,需包含待处理的字段* @param {string} key - 数据对象中需要处理的字段名* @param {string} searchLower - 搜索关键词(小写格式,用于分割字符串)* @returns {Object} - 返回包含原始数据和计算长度信息的新对象*/
splitSortSearchData(data, key, searchLower) {// 使用搜索词分割目标字符串,生成数组(例如:"testabc"用"a"分割得到["test", "bc"])const keySplitArr = data[key].split(searchLower)/*** 计算剩余部分总长度的工具函数* @param {Array} endArr - 分割后的剩余部分数组* @returns {number} - 剩余部分字符总长度*/const comptedEndLen = endArr => endArr.map(item => item.length).reduce((x, y) => x + y, 0)// 获取分割后的剩余部分(排除第一个匹配项之前的内容)let endLenArr = keySplitArr.slice(1)let endLen = 0/* 计算剩余部分总长度逻辑:1. 当剩余部分只有1个元素时,直接取长度(需排除空字符串情况)2. 多个元素时累加各段长度3. 无剩余元素时保持默认值0*/if (endLenArr.length == 1) {// 处理单个剩余元素的情况(例如:精确匹配结尾时可能产生空字符串)if (endLenArr[0]) endLen = endLenArr[0].length// 多个剩余元素时计算总长度(例如:多次匹配产生的多段文本)} else if (endLenArr.length > 1) endLen = comptedEndLen(endLenArr)// 返回增强后的数据对象,包含:// - 原始数据的所有属性// - startLen: 第一个匹配项前的字符长度(用于判断匹配位置)// - endLen: 匹配项之后所有剩余字符总长度(用于相关性排序)return {...data,// 保留原始数据startLen: keySplitArr[0].length,// 首个分割段的长度(搜索词首次出现前的字符数)endLen,// 后续所有分割段的字符总数(越小说明匹配越靠前/内容越相关)}
}/*** 拼接唯一key* @param {* Object} item 计算好前后空格的每一项* @param {* Number} idx* @returns string*/
calcOnlyKey(item, idx) {return `${(item.startLen + 1) * 1000 + item.endLen}д${idx}`
}
计算完成合并计算结果
let mapKeys = []
allKeys.forEach((item, idx) => {/* 将完全匹配的加入到最终数组中 */endResultData = endResultData.concat(allKeyObj[item].equalArr)/** 针对每一类数据进行排序*/mapKeys = mapKeys.concat(allKeyObj[item].hasMapKeys.sort((a, b) => Number(a.split('д')[0]) - Number(b.split('д')[0])))
})
mapKeys.map(mkey => endResultData.push(resultListMap.get(mkey)))
endResultData.concat(otherItems)
console.log('筛选出来数据长度:', endResultData.length)

完整代码

// 搜索函数
onSearch(dataList) {let endResultData = []// 搜索关键词转为小写字母const searchLower = this.searchText.toLocaleLowerCase().trim()// 所有支持搜索的字段,且此处也是搜索的优先级顺序。let allKeys = ['b', 'c', 'j', 'zjf', 'v', 'w', 'x']// 储存优先级字段let allKeyObj = {}// 构造存储不匹配搜索项key的mapallKeys.map(item => {Reflect.set(allKeyObj, item, {equalArr: [],hasMapKeys: [],})})let otherItems = []let resultListMap = new Map()dataList.map((item, idx) => {const filterText = item.filterText// 匹配策略if (filterText.includes(searchLower)) {let equalKeylet hasKeyallKeys.map(keyIt => {let str = String(item[keyIt]).toLocaleLowerCase()if (str === searchLower) {equalKey = keyIt} else if (str.includes(searchLower)) {hasKey = keyIt}})if (equalKey) {const splitItem = this.splitSortSearchData(item, equalKey, searchLower)allKeyObj[equalKey].equalArr.push(splitItem)} else {if (hasKey) {const splitItem = this.splitSortSearchData(item, hasKey, searchLower)const key = this.calcOnlyKey(splitItem, idx)allKeyObj[hasKey].hasMapKeys.push(key)resultListMap.set(key, splitItem)} else otherItems.push(item)}}})let mapKeys = []allKeys.forEach((item, idx) => {/* 将完全匹配的加入到最终数组中 */endResultData = endResultData.concat(allKeyObj[item].equalArr)/** 针对每一类数据进行排序*/mapKeys = mapKeys.concat(allKeyObj[item].hasMapKeys.sort((a, b) => Number(a.split('д')[0]) - Number(b.split('д')[0])))})mapKeys.map(mkey => endResultData.push(resultListMap.get(mkey)))endResultData.concat(otherItems)console.log('筛选出来数据长度:', endResultData.length)return endResultData
}/**
* 对指定字段进行分割,并计算匹配部分的长度信息(用于搜索结果排序或高亮处理)
* @param {Object} data - 原始数据对象,需包含待处理的字段
* @param {string} key - 数据对象中需要处理的字段名
* @param {string} searchLower - 搜索关键词(小写格式,用于分割字符串)
* @returns {Object} - 返回包含原始数据和计算长度信息的新对象
*/
splitSortSearchData(data, key, searchLower) {// 使用搜索词分割目标字符串,生成数组(例如:"testabc"用"a"分割得到["test", "bc"])const keySplitArr = data[key].split(searchLower)/*** 计算剩余部分总长度的工具函数* @param {Array} endArr - 分割后的剩余部分数组* @returns {number} - 剩余部分字符总长度*/const comptedEndLen = endArr => endArr.map(item => item.length).reduce((x, y) => x + y, 0)// 获取分割后的剩余部分(排除第一个匹配项之前的内容)let endLenArr = keySplitArr.slice(1)let endLen = 0/* 计算剩余部分总长度逻辑:1. 当剩余部分只有1个元素时,直接取长度(需排除空字符串情况)2. 多个元素时累加各段长度3. 无剩余元素时保持默认值0*/if (endLenArr.length == 1) {// 处理单个剩余元素的情况(例如:精确匹配结尾时可能产生空字符串)if (endLenArr[0]) endLen = endLenArr[0].length// 多个剩余元素时计算总长度(例如:多次匹配产生的多段文本)} else if (endLenArr.length > 1) endLen = comptedEndLen(endLenArr)// 返回增强后的数据对象,包含:// - 原始数据的所有属性// - startLen: 第一个匹配项前的字符长度(用于判断匹配位置)// - endLen: 匹配项之后所有剩余字符总长度(用于相关性排序)return {...data,// 保留原始数据startLen: keySplitArr[0].length,// 首个分割段的长度(搜索词首次出现前的字符数)endLen,// 后续所有分割段的字符总数(越小说明匹配越靠前/内容越相关)}
}
/**
* 拼接唯一key
* @param {* Object} item 计算好前后空格的每一项
* @param {* Number} idx
* @returns string
*/
calcOnlyKey(item, idx) {return `${(item.startLen + 1) * 1000 + item.endLen}д${idx}`
}

小结

文章最后欢迎各位大佬留言讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93983.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93983.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux学习-网络编程2

1.tcp可能出现粘包解决:要让消息之间有边界1.结束标志 \r\n2.固定长度3.协议结构体2.recv和sendrecv原型:ssize_t recv(int sockfd, void *buf, size_t len, int flags); 功能:从sockfd接收信息 参数:sockfd:要…

【普通地质学】构造运动与地质构造

名词解释走向:倾斜的层面与水平面的交线走向线,走向线两端延伸的方向即为走向;构造运动:由于地球内部动力引起的组成岩石圈物质的机械运动,也可称地壳运动或岩石圈运动;按方向分为垂直运动和水平运动&#…

基于Python的旅游推荐系统 Python+Django+Vue.js

本文项目编号 25009 ,文末自助获取源码 \color{red}{25009,文末自助获取源码} 25009,文末自助获取源码 目录 一、系统介绍1.1 用户功能描述1.2 管理员功能描述 二、系统录屏三、启动教程四、功能截图五、文案资料5.1 选题背景5.2 国内外研究…

基于51单片机的智能加湿器设计 温湿度水位防干烧手动自动声光报警

1 系统功能介绍 本设计实现了一种 基于 51 单片机的智能加湿器控制系统。随着现代生活水平的提高,人们对居住和办公环境的舒适度要求越来越高,空气湿度和温度的调节逐渐成为家庭和办公自动化的重要组成部分。传统加湿器仅能实现简单的加湿功能&#xff0…

开发避坑指南(31):Oracle 11g LISTAGG函数使用陷阱,缺失WITHIN子句解决方案

错误信息 Error querying database. Cause: java.sql.SQLSyntaxErrorException: ORA-02000: 缺失 WITHIN 关键字查询语句 使用LISTAGG函数将多行数据合并为单行字符串,如下: selectt.order_no as orderNo,t.account_no,(select listagg(a.bank_name,,) …

【虚拟化】磁盘置备方式的性能损耗对比

【虚拟化】磁盘置备方式的性能损耗对比摘要1、定义1.1厚置备(Thick Provisioning)1.2厚置备延迟置零(Thick Provisioned Lazy Zeroed)1.3厚置备置零(Thick Provisioned Eager Zeroed)2、对比摘要 探索三种…

计算机网络:TCP、UDP

一、TCP粘包问题(一)什么是粘包?TCP粘包是指发送方发送的多个数据包在接收方接收时被合并成一个大的数据包的现象。这种现象是由于TCP协议本身的特性导致的,TCP是面向流的协议,数据在传输过程中没有明确的边界。&#…

使用 Google 开源 AI 工具 LangExtract 进行结构化信息抽取

导读:本文介绍科技大厂 Google 2025年 7 月最新开源的 Python 库:LangExtract,用于从非结构文本提取结构化数据,以及非官方的 Javascript、Rust 语言实现版本。 文章目录一、关于 LangExtract1.1 需求痛点1.2 LangExtract1.3 参考…

把 AI 变成「会说话的盲道」——基于骨传导的地砖级语音导盲砖

标签:城市无障碍、骨传导、TinyML、语音导航、太阳能、离线推理、ESP32-C3、边缘 AI ---- 1. 背景:为什么盲道要开口说话? 全国 1700 万视障者,城市道路却常出现: • 盲道被违停车、广告牌截断; • 传统导…

解析三品汽车零部件PLM系统解决方案:如何助力行业解决研发管理难题

2024年,全球汽车零部件市场规模超1.5万亿美元,中国市场规模达4.6万亿元人民币。产业繁荣高度依赖汽车产业的发展,2024年中国汽车产销量均突破3100万辆,新能源汽车销量约1286万辆,2019-2024年复合增长率达76.59%。当前行…

【RA-Eco-RA4E2-64PIN-V1.0 开发板】步进电机驱动

【RA-Eco-RA4E2-64PIN-V1.0 开发板】步进电机驱动 本文介绍了 RA-Eco-RA4E2-64PIN-V1.0 开发板驱动 28BYJ-48 步进电机的设计。 项目介绍 硬件连接:28BYJ-48 步进电机、ULN2003 驱动板、Jlink 调试器等;工程创建:GPIO 和 UART 的配置&#xf…

机器人爆发、汽车换代,速腾聚创开始讲新故事

文|刘俊宏编|王一粟2025年智能汽车出货量激增,堪称“智驾安全带”的激光雷达,迎来了自己的iPhone时刻。8月21日,速腾聚创发布了2025年第二季度及中期业绩报告,激光雷达的中场战事得以一并揭开。速腾聚创二季…

在Excel和WPS表格中如何隐藏单元格的公式

Excel和WPS表格中有数据、公式、图表等以后,要发给他人查阅,如果不希望表格中的公式被查阅和修改,我们可以通过两个步骤把公式隐藏起来。先设置有公式的单元格格式为隐藏,然后保护工作表即可。第一步:设置单元格格式为…

Eino 开源框架全景解析 - 以“大模型应用的搭积木指南”方式理解(一)

Eino 开源框架全景解析 - 大模型应用的搭积木指南 🎯 什么是 Eino?一句话概括 Eino 是字节跳动开源的大语言模型应用开发框架,就像是一个专门为 AI 应用设计的"搭积木工具箱",让开发者能够像搭乐高一样轻松构建复杂的 A…

大语言模型原理(Transformer架构)

一、概览1.1 定义大语言模型(LLM)是基于深度学习和神经网络的自然语言处理技术,目前主要通过Transformer架构和大规模数据训练来理解和生成语言。GPT不同架构的训练参数:GPT-1(2018):1.17亿参数GPT-2(2018)&#xff1a…

Nginx npm + Node.js 简单实践

一、基本概念介绍 Nginx 是一款高性能的 Web 服务器和反向代理服务器,而 Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境,可以让JavaScript 在服务器端运行。npm 则是 Node.js 的默认包管理工具,类似手机的应用市场。主要功能事故…

Python 中 SQLAlchemy 和 MySQLdb 的关系

目录1. 角色和定位2. 工作原理和交互方式使用纯 MySQLdb使用 SQLAlchemy(核心或 ORM)3. 依赖关系总结与选择 简单来说,它们的关系是:SQLAlchemy 是一个高层抽象的对象关系映射器(ORM)和 SQL 工具包&#xf…

【CV】OpenCV①——图形处理简介

一、OpenCV简介 1. 图像处理 1.1. 图像起源 1.1.1. 图像是什么1.1.2. 模拟图像和数字图像1.2. 数字图像的表示 1.2.1. 位数1.2.2. 图像分类 二值图像灰度图彩色图

JAVA后端开发——API状态字段设计规范与实践

1. 引言在现代Web应用与API设计中,状态(Status)字段的管理是一个普遍存在且至关重要的议题。状态字段,如订单状态、任务执行状态、模型运行状态等,直接关系到系统的核心业务逻辑。不恰当的设计会导致API可读性差、系统…

【MySQL的卸载】

MySQL的卸载卸载MySQL步骤1:停止MySQL服务步骤2:软件的卸载卸载方式一:通过控制面板卸载软件卸载方式二:通过360或电脑管家等软件卸载卸载方式三:通过安装包提供的卸载功能卸载步骤3:残余文件的清理步骤4&a…