v-html在小程序中失效因安全策略限制非白名单标签,仅被允许;全端兼容需用rich-text组件配合正则匹配生成nodes数组,关键词须正则转义。

直接用 v-html + replace() 最简单,但小程序里会失效;真要全端兼容,得用 rich-text 或拆分数组渲染,否则 iOS 和微信小程序上高亮不显示、样式丢失、甚至被过滤。
为什么 v-html 在小程序里不生效
微信小程序和 uni-app 编译到小程序平台时,v-html 渲染的 HTML 标签(如 、)会被原生 WebView 拒绝或自动剥离,只保留纯文本。只有 标签 + rich-text 组件能稳定生效。
- 现象:
v-html="item.title.replace(/关键词/g, '关键词')"在 H5 正常,小程序里红色消失、只剩文字 - 原因:小程序安全策略限制非白名单标签,
不在其中,是少数被允许的渲染标签 - 正确写法必须配合
rich-text,且内容为字符串数组或带name字段的对象数组 - uni-app 的
rich-text不支持内联style,颜色/字体粗细需靠或外层 class 控制
rich-text 高亮的最小可行写法
不用第三方库、不依赖 split() 复杂逻辑,用正则 + matchAll() 定位再拼接,生成符合 rich-text 要求的 nodes 数组。
- 关键点:先用
text.matchAll(regex)找出所有匹配位置,避免split()破坏连续匹配(比如关键词是“搜”,原文“搜索搜狗”会被错误切开) - 必须对关键词做正则转义:
keyword.replace(/[.*+?^${}()|[]\]/g, '\$&'),否则输入+、[会报错 - 生成
nodes示例:const nodes = [];let lastIndex = 0;for (const match of text.matchAll(regex)) {if (match.index > lastIndex) {nodes.push({ name: 'text', children: [{ text: text.slice(lastIndex, match.index) }] });}nodes.push({ name: 'font', attrs: { color: '#DF2D45' }, children: [{ text: match[0] }] });lastIndex = match.index + match[0].length;}if (lastIndex - 模板中直接:
Vue2 和 Vue3 的响应式差异处理
Vue2 的 data 返回对象,Vue3 的 setup 需用 ref 或 computed 包裹 nodes;但更关键的是 watch 触发时机——不能只监听 keyword,得监听搜索结果列表变化(比如接口返回新数据后才高亮)。
- 常见错误:watch
keyword,但用户没输完就触发,导致空 keyword 替换出错;或者 keyword 改变但 list 没更新,高亮还是旧的 - 推荐写法:watch
searchResultList(即渲染的数据源),内部再取当前keyword做高亮,确保数据与关键词同步 - Vue3 中若用
computed,注意不要在计算属性里调用异步方法;高亮逻辑必须是纯同步的字符串处理 - 防抖不是可选项:输入框
@input必须加debounce(建议 300ms),否则快速打字时频繁触发正则匹配,iOS 小程序容易卡顿
颜色和样式在各端的实际表现
在微信小程序里能变色,但在 H5 和 App 端可能被忽略;真正跨端一致的方式,是用 rich-text 的 name: 'text' + class 控制,但 class 只对外层容器生效,无法作用于单个片段。
- 最稳方案:放弃内联样式,改用
组件封装(仅限 nvue 页面),或用u-text(uView)这类支持 slot 插槽的组件手动拆分 - 如果坚持用
rich-text,iOS 微信小程序里font的size属性无效,color有效;安卓部分机型对font支持不全,建议 fallback 到加粗:+rich-text的attrs.style(仅 H5 有效) - 别碰
style字段传 CSS:uni-app 文档明确说明rich-text的style属性只影响整个组件,不作用于子节点
最易被忽略的一点:搜索词为空时,new RegExp('') 会匹配每一个字符,导致整段文字全被包裹,页面炸开。每次生成正则前必须加 if (!keyword.trim()) return originalText 校验。











