馃攳缁欐垜鐨勫皬绋嬪簭鍔犱簡涓笣婊戠殑鎼滅储鍔熻兘锛岃俯鍧戣〃鎯呭寘闀垮害闂

343 闃呰8鍒嗛挓

鍓嶈█

鏈�杩戝湪鐢ㄨ嚜宸辩殑鍗$洅灏忕▼搴忕殑鏃跺�欙紝鍙戠幇鍗$墖瓒婃潵瓒婂锛屾湁鏃跺�欒鎵惧埌鏌愪竴寮犳潵鐪嬬湅绗旇瑕佹壘鍗婂ぉ锛屼簬鏄嚜宸卞仛浜嗕竴涓悳绱㈠姛鑳斤紝鍏堢湅鏁堟灉锛�/p> 鎼滅储.gif

鎬庝箞鏍凤紝鏄笉鏄繕鎸轰笉閿欑殑锛岄偅涔堣繖绡囨枃绔犲氨璁茶杩欐牱涓�涓悳绱㈠睍绀虹殑鍔熻兘鏄浣曞疄鐜扮殑銆�/p>

浠g爜瀹炵幇

棣栧厛鎴戜滑鍒嗘瀽杩欎釜鎼滅储鍔熻兘鍖呭惈鍝簺闇�瑕佸疄鐜扮殑鐐癸細

  1. 杈撳叆鍏抽敭瀛楋紝鍖归厤瀵瑰簲鐨勫崱鐗囧睍绀�/li>
  2. 鍖归厤鐨勫崱鐗囦笂闈紝瑕佹妸绗﹀悎鎼滅储鏉′欢鐨勬墍鏈夎瘝缁欓珮浜睍绀哄嚭鏉ワ紝涓嶇劧鏂囨湰澶氱殑鏃跺�欏鏄撶湅鑺变簡銆�/li>
  3. 鐐瑰嚮鍖归厤鍒扮殑鍗$墖锛岃璺宠浆鍒板崱鐗囩殑浣嶇疆锛屽苟涓旈棯鐑佷袱涓嬶紝杩欐牱涓�鐪嬪氨鐭ラ亾瑕佹壘鍗$墖鍦ㄥ摢浜嗐��/li>

鍒嗘瀽瀹屾垚鍚庯紝鎴戜滑涓�鐐逛竴鐐瑰疄鐜般��/p>

鍖归厤鍏抽敭瀛�/h3>

鎴戠殑灏忕▼搴忕洰鍓嶆槸绾墠绔悳绱㈢殑锛屽彧鏄洰鍓嶆槸杩欐牱锛屾墍浠ユ悳绱㈤�昏緫涔熸槸鍦ㄥ墠绔疄鐜般�傛悳绱㈤�昏緫濡傛灉绠�鍗曞疄鐜扮殑璇濆氨鏄皢鎼滅储妗嗙殑鍐呭涓庡垪琛ㄤ腑鐨勬瘡涓�椤硅繘琛屽姣旓紝鐪嬬湅鍐呭涓湁娌℃湁鍖呭惈杩欎釜瀛楃涓茬殑锛屽鏋滄湁灏辨妸杩欎釜椤圭粰杩斿洖鍥炲幓銆�/p>

鍏堢湅浠g爜锛�/p>

const onSearch = () => {
  // 妫�鏌ユ悳绱㈡枃鏈鏄惁鏈夊��/span>
  if (searchText.value) {
    // 鍒涘缓涓�涓鍒欒〃杈惧紡瀵硅薄锛岀敤浜庡湪鍗$墖鍐呭涓悳绱㈡枃鏈�/span>
    // 'giu' 鏍囧織琛ㄧず鍏ㄥ眬鎼滅储銆佷笉鍖哄垎澶у皬鍐欏拰鏀寔 Unicode
    const searchTextRegex = new RegExp(searchText.value, 'giu')
 
    // 閬嶅巻鎵�鏈夌殑鍗$墖鐩掑瓙
    const matchCardBox = cardDataStore.cardBoxList.map((cardBox) => {
      // 瀵规瘡涓崱鐗囩洅瀛愶紝鍒涘缓涓�涓柊瀵硅薄锛屽寘鍚師濮嬪睘鎬у拰淇敼鍚庣殑鍗$墖椤圭洰
      return {
        ...cardBox,
        // 鏄犲皠骞惰繃婊ゅ崱鐗囬」鐩紝鍙繚鐣欏尮閰嶆悳绱㈡枃鏈殑椤圭洰
        cardItems: cardBox.cardItems
          .map((cardItem) => {
            // 鍒濆鍖栧墠闈㈠拰鑳岄潰鍐呭鐨勫尮閰嶆暟缁�/span>
            const frontMatches = []
            const backMatches = []
 
            let match
            // 鍦ㄥ崱鐗囧墠闈㈠唴瀹逛腑鎼滅储鍖归厤椤�/span>
            while ((match = searchTextRegex.exec(cardItem.frontContent)) !== null) {
              // 璁板綍姣忎釜鍖归厤椤圭殑璧峰鍜岀粨鏉熺储寮�/span>
              frontMatches.push({
                startIndex: match.index,
                endIndex: match.index + match[0].length,
              })
            }
 
            // 閲嶇疆姝e垯琛ㄨ揪寮忕殑 lastIndex 灞炴�э紝浠ヤ究閲嶆柊鎼滅储
            searchTextRegex.lastIndex = 0
            // 鍦ㄥ崱鐗囪儗闈㈠唴瀹逛腑鎼滅储鍖归厤椤�/span>
            while ((match = searchTextRegex.exec(cardItem.backContent)) !== null) {
              // 璁板綍姣忎釜鍖归厤椤圭殑璧峰鍜岀粨鏉熺储寮�/span>
              backMatches.push({
                startIndex: match.index,
                endIndex: match.index + match[0].length,
              })
            }
 
            // 妫�鏌ユ槸鍚︽湁鍖归厤椤癸紙鍓嶉潰鎴栬儗闈級
            const isMatched = frontMatches.length > 0 || backMatches.length > 0
 
            // 杩斿洖涓�涓柊鐨勫崱鐗囬」鐩璞★紝鍖呭惈鏄惁鍖归厤鍜屽尮閰嶉」鐨勪綅缃�/span>
            return {
              ...cardItem,
              isMatched,
              frontMatches,
              backMatches,
            }
          })
          // 杩囨护鎺変笉鍖归厤鐨勯」鐩�/span>
          .filter((item) => item.isMatched),
      }
    })
 
    // 杩囨护鎺夋病鏈夊尮閰嶉」鐩殑鍗$墖鐩掑瓙
    filteredCards.value = matchCardBox.filter((cardBox) => cardBox.cardItems.length > 0)
  } else {
    // 濡傛灉娌℃湁鎼滅储鏂囨湰锛屽垯娓呯┖杩囨护鍚庣殑鍗$墖鍒楄〃
    filteredCards.value = []
  }
}

1. 鍒涘缓姝e垯琛ㄨ揪寮�/h4>
const searchTextRegex = new RegExp(searchText.value, 'giu') 
  • searchText.value锛氳繖鏄敤鎴疯緭鍏ョ殑鎼滅储鏂囨湰銆�/li>
    • new RegExp(...) 锛氶�氳繃浼犲叆鐨勬悳绱㈡枃鏈拰鏍囧織锛�'giu'锛夊垱寤轰竴涓柊鐨勬鍒欒〃杈惧紡瀵硅薄銆�/li>
    • g锛氬叏灞�鎼滅储鏍囧織锛岃〃绀烘悳绱㈡暣涓瓧绗︿覆涓殑鎵�鏈夊尮閰嶉」锛岃�屼笉鏄湪鎵惧埌绗竴涓尮閰嶉」鍚庡仠姝€��/li>
    • i锛氫笉鍖哄垎澶у皬鍐欐爣蹇楋紝琛ㄧず鎼滅储鏃跺拷鐣ュぇ灏忓啓宸紓銆� - u锛歎nicode 鏍囧織锛岃〃绀哄惎鐢� Unicode 瀹屾暣鍖归厤妯″紡锛岃繖瀵逛簬澶勭悊闈� ASCII 瀛楃寰堥噸瑕併��/li>

2. 鎼滅储鍖归厤椤�/h4>
let match 
  • let match锛氬0鏄庝竴涓彉閲� match锛屽畠灏嗙敤浜庡瓨鍌� RegExp.exec() 鏂规硶鎵惧埌鐨勫尮閰嶉」銆�/li>
while ((match = searchTextRegex.exec(cardItem.frontContent)) !== null) { // ... } 
  • searchTextRegex.exec(cardItem.frontContent) 锛氬湪 cardItem.frontContent 锛堝崱鐗囩殑姝i潰鍐呭锛変腑鎵ц姝e垯琛ㄨ揪寮忔悳绱€��/li>
    • 濡傛灉鎵惧埌鍖归厤椤癸紝exec() 鏂规硶杩斿洖涓�涓暟缁勶紝鍏朵腑绗竴涓厓绱狅紙match[0]锛夋槸鎵惧埌鐨勫尮閰嶆枃鏈紝index 灞炴�ф槸鍖归厤椤瑰湪瀛楃涓蹭腑鐨勮捣濮嬩綅缃��/li>
  • 濡傛灉娌℃湁鎵惧埌鍖归厤椤癸紝exec() 鏂规硶杩斿洖 null銆� - while 寰幆锛氱户缁墽琛岋紝鐩村埌 exec() 鏂规硶杩斿洖 null锛岃〃绀烘病鏈夋洿澶氱殑鍖归厤椤广��/li>

3. 璁板綍鍖归厤椤圭殑绱㈠紩

frontMatches.push({ startIndex: match.index, endIndex: match.index + match[0].length, }) 
  • 鍦ㄦ瘡娆″惊鐜凯浠d腑锛岄兘浼氭壘鍒颁竴涓尮閰嶉」銆�/li>
  • startIndex锛氬尮閰嶉」鍦� cardItem.frontContent 涓殑璧峰浣嶇疆銆�/li>
  • endIndex锛氬尮閰嶉」鍦� cardItem.frontContent 涓殑缁撴潫浣嶇疆锛堝嵆璧峰浣嶇疆鍔犱笂鍖归厤鏂囨湰鐨勯暱搴︼級銆�/li>
  • frontMatches.push(...)锛氬皢鍖呭惈璧峰鍜岀粨鏉熺储寮曠殑瀵硅薄娣诲姞鍒� frontMatches 鏁扮粍涓��/li>

缁忚繃杩欎箞涓�鐣搷浣滐紝鎴戜滑灏卞彲浠ヨ幏寰椾竴涓瓫閫夊悗鐨勬暟缁勶紝鍏朵腑鍖呭惈浜嗘墍鏈夊尮閰嶇殑椤癸紝姣忎釜椤硅繕鏈変竴涓簩缁存暟缁勭敤鏉ヨ褰曞尮閰嶄綅缃紑澶寸粨灏剧殑绱㈠紩锛�/p>

cardItems: (CardItem & {
      id: string
      frontMatches?: { startIndex: number; endIndex: number }[]
      backMatches?: { startIndex: number; endIndex: number }[]
    })[]

涓轰粈涔堣澶ц垂鍛ㄧ珷鐨勮褰曡繖涓储寮曞憿锛岄偅鏄洜涓轰笅涓�姝ラ渶瑕佺敤鍒帮紝鎺ヤ笅鏉ヨ璇村叧閿瘝楂樹寒鐨勫睍绀猴細

鍏抽敭璇嶉珮浜�/h3> image.png

鍏抽敭璇嶉珮浜渶瑕佸湪瀛楃涓茬殑鏌愬嚑涓瓧绗︿腑鏇存敼瀹冪殑鏍峰紡锛屽洜姝ゆ垜浠笂涓�姝ユ墠闇�瑕佽褰曚竴涓嬮渶瑕侀珮浜殑瀛楃涓插紑濮嬪拰缁撴潫鐨勪綅缃紝濡傛涓�鏉ユ垜浠仛杩欎釜楂樹寒鐨勭粍浠跺氨涓嶇敤鍐嶆墽琛屼竴娆″尮閰嶄簡銆傞偅涔堣繖涓牱寮忚濡備綍瀹炵幇鍛紝鎴戜滑闇�瑕侀亶鍘嗚繖涓瓧绗︿覆锛屽湪闇�瑕侀珮浜殑瀛楀鍔犻澶栫殑鏍峰紡锛屾渶鍚庡啀閲嶆柊鎷兼帴鎴愪竴涓瓧绗︿覆銆�/p>

// Highlight.vue
<template>
  <view class="flex flex-wrap">
    <view
      v-for="(charWithStyle, index) in styledText"
      class="text-sm"
      :key="index"
      :class="charWithStyle.isMatched ? 'text-indigo-500 font-semibold' : ''"
    >
      {{ charWithStyle.char }}
    </view>
  </view>
</template>

<script lang="ts" setup>
import { defineProps, computed } from 'vue'

interface Props {
  text: string
  matches: { startIndex: number; endIndex: number }[]
}

const props = defineProps<Props>()

const styledText = computed(() => {
  const textArray = _.toArray(props.text)
  const returnArr = []
  let index = 0
  let arrIndex = 0
  while (index < props.text.length) {
    let char = ''
    if (textArray[arrIndex].length > 1) {
      char = textArray[arrIndex]
    } else {
      char = props.text[index]
    }

    const isMatched = props.matches.some((match) => {
      const endIndex = match.endIndex
      const startIndex = match.startIndex

      return startIndex <= index && index < endIndex
    })
    returnArr.push({ char, isMatched })
    index += textArray[arrIndex].length
    arrIndex += 1
  }

  return returnArr
})
</script>

杩欓噷鎴戞病鏈変娇鐢� for of 鐩存帴閬嶅巻瀛楃涓诧紝杩欎篃鏄垜鐨勪竴涓俯鍧戠偣锛屽儚 emoji 琛ㄦ儏杩欑瀛楃瀹冪殑闀垮害鍏跺疄涓嶆槸 1锛屽鏋滀綘鐩存帴浣跨敤 for of 鍘婚亶鍘嗕細鎶婂畠鐨勭粨鏋勭粰鎷嗗紑锛屾渶缁堝睍绀哄嚭鏉ョ殑鏄贡鐮侊紝濡傛灉浣犳兂姝e父灞曠ず灏辫鐢� Array.from(props.text) 鐨勬柟寮忓皢瀛楃涓茶浆鎹㈡垚鏁扮粍锛屽啀杩涜閬嶅巻锛岃繖鏍锋瘡涓瓧绗﹀氨閮芥槸瀹屾暣鐨勩��/p>

鍋囪鎴戜滑鎵撳嵃锛�/p>

  console.log('馃槦'[0], '馃槦'.length)
  console.log(Array.from('馃槦')[0], Array.from('馃槦').length)

image.png

杩欓噷鎴戞病鏈変娇鐢� Array.from 鑰屾槸浣跨敤浜� lodash 涓殑 toArray锛屾槸鍥犱负鐪嬪埌杩欑瘒鏂囩珷 fehey.com/emoji-lengt鈥�/a> 涓彁鍒帮細

Array.from(props.text) 鍒涘缓鐨勬暟缁� textArray 涓殑姣忎釜鍏冪礌瀹為檯涓婃槸涓�涓� UTF-16 浠g爜鍗曞厓鐨勫瓧绗︿覆琛ㄧず锛岃�屼笉鏄畬鏁寸殑 Unicode 瀛楃 Emoji 琛ㄦ儏鏈夊彲鑳芥槸澶氫釜 Emoji + 涓�浜涢澶栫殑瀛楃 鏉ユ嫾鎺ュ嚭鏉ョ殑锛屽儚 '馃懇鈥嶐煈┾�嶐煈р�嶐煈�' 灏辨槸鐢� ['馃懇', '', '馃懇', '', '馃懅', '', '馃懅'] 鎷兼帴鑰屾垚鐨勶紝鍗曚釜 Emoji 闀垮害涓� 2锛屼腑闂寸殑杩炴帴瀛楃闀垮害涓� 1锛屾晠杩斿洖浜� 11銆�/p>

濡備綍鑾峰彇 '馃懇鈥嶐煈┾�嶐煈р�嶐煈�' 鐨勯暱搴︿负瑙嗚鐨� 1 鍛紝鍙互浣跨敤 lodash 鐨� toArray 鏂规硶锛宊.toArray('馃懇鈥嶐煈┾�嶐煈р�嶐煈�').length = 1,鍏�a href="http://222.178.203.72:19005/whst/63/=khmjzitdihmzbm>sZqfds=gssor$29$1E$1Efhsgtazbnl$1EkncZrg$1EkncZrg$1Eakna$1E1e68.42c6ab6b8b8450Z2.ccZ1.1a2cbc1a61a8.$1EzhmsdqmZk$1ErsqhmfSn9qqZxzir$12K01/" target="_blank" title="https://github.com/lodash/lodash/blob/2f79053d7bc7c9c9561a30dda202b3dcd2b72b90/.internal/stringToArray.js#L12" ref="nofollow noopener noreferrer">鍐呴儴瀹炵幇聽浜嗗 unicode 瀛楃杞崲鏁扮粍鐨勫吋瀹广��/p>

姝f槸鍥犱负鎴戜滑绗竴姝ヤ腑浣跨敤姝e垯鍘诲尮閰嶅瓧绗︿覆鐨勬椂鍊欙紝鏄牴鎹〃鎯呭寘瀛楃瀹為檯鐨勯暱搴﹁繑鍥炵殑绱㈠紩鍊硷紝鎵�浠ユ垜浠繖閲屾湁涓�涓�昏緫锛�/p>

  let index = 0
  let arrIndex = 0
  while (index < props.text.length) {
    let char = ''
    if (textArray[arrIndex].length > 1) {
      char = textArray[arrIndex]
    } else {
      char = props.text[index]
    }
//锛岋紝锛�/span>
    index += textArray[arrIndex].length
    arrIndex += 1
  }

濡傛灉瀛楃鐨勯暱搴﹀ぇ浜庝竴鎴戜滑灏变粠瀛楃涓叉暟缁勪腑鍙栧�硷紝杩欐牱琛ㄦ儏鍖呭氨鑳芥甯稿睍绀轰簡锛岀劧鍚庣淮鎶や袱涓储寮曪紝涓�涓储寮曠粰瀛楃闀垮害澶т簬1鐨勫瓧绗︾敤锛屼竴涓粰瀛楃闀垮害涓�勭敤锛屾牴鎹笉鍚岀殑鎯呭喌鍙栦笉鍚岀殑鍊硷紝杩欐牱灏辫兘澶勭悊濂借〃鎯呭寘鐨勮繖绉嶆儏鍐典簡銆備笅闈㈣繖绉嶅緢澶氫釜琛ㄦ儏鍖呯殑鏂囨湰锛屼篃鑳藉湪姝g‘鐨勪綅缃珮浜�/p>

image.png

婊氬姩鍒版寚瀹氫綅缃苟楂樹寒

杩欎竴姝ュ氨姣旇緝绠�鍗曚簡锛岀洿鎺ヤ笂浠g爜锛�/p>

import { getCurrentInstance } from 'vue'
const instance = getCurrentInstance()
const { safeAreaInsets } = uni.getWindowInfo()


// 婊氬姩鍒板崱鐩掍綅缃�/span>
const scrollToCardBox = (position: 'top' | 'bottom' = 'top') => {
  const query = uni.createSelectorQuery().in(instance.proxy)
  query
    .select(`#card-box-${props.cardBoxIndex}`)
    .boundingClientRect((data) => {
      return data
    })
    .selectViewport()
    .scrollOffset((data) => {
      return data
    })
    .exec((data) => {
      uni.pageScrollTo({
        scrollTop:
          data[1].scrollTop +
          data[0].top -
          safeAreaInsets.top +
          (position === 'top' ? 0 : data[0].height),
        duration: 200,
      })
    })
}

鍏充簬瑙f瀽锛屽湪 馃ゲ韪╁潙鏃犳暟锛屽浣曠敤 uniapp 瀹炵幇涓�涓笣婊戠殑鑻辫瀛︿範鍗$洅灏忕▼搴�/a> 杩欑瘒鏂囩珷涓湁璇︾粏鎻愬埌锛岃繖閲屼笉璧樿堪浜嗐��/p>

uni.pageScrollTo 鏈変竴涓洖璋冿紝鍙互鐢ㄤ簬婊氬姩鍒版寚瀹氫綅缃悗锛屾墽琛屾煇涓嚱鏁帮紝閭d箞鎴戜滑鍙互鍦ㄨ繖閲岃缃Е鍙戦珮浜殑鍔ㄧ敾锛屽姩鐢荤殑瀹炵幇濡備笅锛�/p>

<view
   //...
    :class="
      cardItemStore.scrollToCardItemId === props.cardItemData.id ? 'animation-after-search' : ''
    "
    
.animation-after-search {
  animation: vague 1s;
  animation-iteration-count: 2;
}

@keyframes vague {
  0%,
  100% {
    box-shadow: inset 0 0 0 0 transparent; /* 鍒濆鍜岀粨鏉熸椂娌℃湁闃村奖 */
  }
  50% {
    box-shadow: inset 0 0 0 2px #6366f1; /* 涓棿鏃跺埢鏄剧ず闃村奖 */
  }
}

杩欓噷娌℃湁浣跨敤杈规锛岃�屾槸浣跨敤浜嗗唴宓岀殑闃村奖锛岄伩鍏嶈竟妗嗕細鎶婂鍣ㄦ拺澶х殑闂锛屾粴鍔ㄥ畬鎴愬悗鍔ㄦ�佺粰鎸囧畾鍏冪礌涓�涓墽琛屽姩鐢荤殑 class,鍔ㄧ敾瑙﹀彂瀹屾垚鍚庡啀绉婚櫎 class 灏� OK 浜嗐�傛晥鏋滃涓嬶細

婊氬姩楂樹寒.gif

鎬荤粨

濡傛灉涓嶆槸閬囧埌浜嗚〃鎯呭寘闀垮害闂锛岃繖涓悳绱㈠姛鑳界殑瀹炵幇杩樻槸姣旇緝绠�鍗曠殑锛岄噸鐐规槸浜や簰鍜岃璁℃槸鍚﹁兘澶熻鐢ㄦ埛蹇�熷畾浣嶅埌鎯虫壘鐨勫唴瀹广�傜洰鍓嶆槸绾墠绔疄鐜帮紝鑰屼笖娑夊強浜嗗緢澶氶亶鍘嗭紝鎬ц兘杩樻湁寰呮彁鍗囷紝涓嶈繃鍏堝疄鐜板啀浼樺寲浜嗐�傚涔犲崱鐩掑凡缁忎笂绾夸簡锛屽ぇ瀹跺彲浠ョ洿鎺ユ悳绱㈠埌锛岃繖涓悳绱㈠姛鑳藉緢蹇篃鍙戠増浜嗭紝娆㈣繋浣撻獙銆�/p>