当前位置: 首页 > news >正文

前端本地模糊搜索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.xdnf.cn/news/1329625.html

相关文章:

  • 土地财政历史探寻
  • Diamond开发经验(1)
  • RabbitMQ:SpringAMQP Direct Exchange(直连型交换机)
  • 走进数字时代,融入数字生活,构建数字生态
  • Arthas 全面使用指南:离线安装 + Docker/K8s 集成 + 集中管理
  • 开源 C++ QT Widget 开发(一)工程文件结构
  • 猫头虎AI分享|字节开源了一款具备长期记忆能力的多模态智能体:M3-Agent 下载、安装、配置、部署教程
  • Java NIO 核心精讲(上):Channel、Buffer、Selector 详解与 ByteBuffer 完全指南
  • Python量化交易:结合爬虫与TA-Lib技术指标分析
  • Vue2.x核心技术与实战(二)
  • 力扣hot100:三数之和(排序 + 双指针法)(15)
  • Android Cordova 开发 - Cordova 嵌入 Android
  • 谷歌为什么要将Android的页面大小(Page Size)从传统的4KB升级至16KB
  • Android RxJava数据库操作:响应式改造实践
  • Android-ContentProvider的跨应用通信学习总结
  • 2943. 最大化网格图中正方形空洞的面积
  • MCP(模型上下文协议):是否是 AI 基础设施中缺失的标准?
  • 电源、电流及功率实测
  • 【图像算法 - 18】慧眼辨良莠:基于深度学习与OpenCV的麦田杂草智能识别检测系统(附完整代码)
  • RabbitMQ:SpringAMQP 入门案例
  • 【自动驾驶】8月 端到端自动驾驶算法论文(arxiv20250819)
  • 最新研究进展:2023-2025年神经机器翻译突破性成果
  • 【LeetCode】17. 电话号码的字母组合
  • idea中如何设置文件的编码格式
  • 【撸靶笔记】第七关:GET - Dump into outfile - String
  • Python爬虫实战:研究ICP-Checker,构建ICP 备案信息自动查询系统
  • 【MySQL】--- 库表操作
  • 字节开源了一款具备长期记忆能力的多模态智能体:M3-Agent
  • 【数据结构】堆和二叉树详解(下)
  • 构建自主企业:AgenticOps 的技术蓝图