搜索页面新增搜索内容高亮显示,并处理返回数据格式,拼接展示

This commit is contained in:
wangyifeng 2024-12-27 16:57:32 +08:00
parent ad658710f7
commit 4f43f1a001
7 changed files with 230 additions and 115 deletions

View File

@ -39,7 +39,7 @@
"quill-mention": "^6.0.2",
"vconsole": "^3.15.1",
"vue": "^3.3.8",
"vue-i18n": "^9.6.5"
"vue-i18n": "11.0.0-rc.1"
},
"devDependencies": {
"@dcloudio/types": "^3.4.7",

View File

@ -93,8 +93,8 @@ importers:
specifier: ^3.3.8
version: 3.4.35(typescript@5.5.4)
vue-i18n:
specifier: ^9.6.5
version: 9.13.1(vue@3.4.35(typescript@5.5.4))
specifier: 11.0.0-rc.1
version: 11.0.0-rc.1(vue@3.4.35(typescript@5.5.4))
devDependencies:
'@dcloudio/types':
specifier: ^3.4.7
@ -1391,26 +1391,26 @@ packages:
'@iconify/utils@2.1.32':
resolution: {integrity: sha512-LeifFZPPKu28O3AEDpYJNdEbvS4/ojAPyIW+pF/vUpJTYnbTiXUHkCh0bwgFRzKvdpb8H4Fbfd/742++MF4fPQ==}
'@intlify/core-base@11.0.0-rc.1':
resolution: {integrity: sha512-fnfZoa9pb1dKM3L1UkDUGLLrPFQ2BK98x4/fMqwS/fktUor34vQR/itPtfv652ZTplenXXLCEYjUYTGfDZgMTQ==}
engines: {node: '>= 16'}
'@intlify/core-base@9.1.9':
resolution: {integrity: sha512-x5T0p/Ja0S8hs5xs+ImKyYckVkL4CzcEXykVYYV6rcbXxJTe2o58IquSqX9bdncVKbRZP7GlBU1EcRaQEEJ+vw==}
engines: {node: '>= 10'}
'@intlify/core-base@9.13.1':
resolution: {integrity: sha512-+bcQRkJO9pcX8d0gel9ZNfrzU22sZFSA0WVhfXrf5jdJOS24a+Bp8pozuS9sBI9Hk/tGz83pgKfmqcn/Ci7/8w==}
engines: {node: '>= 16'}
'@intlify/devtools-if@9.1.9':
resolution: {integrity: sha512-oKSMKjttG3Ut/1UGEZjSdghuP3fwA15zpDPcjkf/1FjlOIm6uIBGMNS5jXzsZy593u+P/YcnrZD6cD3IVFz9vQ==}
engines: {node: '>= 10'}
'@intlify/message-compiler@11.0.0-rc.1':
resolution: {integrity: sha512-TGw2uBfuTFTegZf/BHtUQBEKxl7Q/dVGLoqRIdw8lFsp9g/53sYn5iD+0HxIzdYjbWL6BTJMXCPUHp9PxDTRPw==}
engines: {node: '>= 16'}
'@intlify/message-compiler@9.1.9':
resolution: {integrity: sha512-6YgCMF46Xd0IH2hMRLCssZI3gFG4aywidoWQ3QP4RGYQXQYYfFC54DxhSgfIPpVoPLQ+4AD29eoYmhiHZ+qLFQ==}
engines: {node: '>= 10'}
'@intlify/message-compiler@9.13.1':
resolution: {integrity: sha512-SKsVa4ajYGBVm7sHMXd5qX70O2XXjm55zdZB3VeMFCvQyvLew/dLvq3MqnaIsTMF1VkkOb9Ttr6tHcMlyPDL9w==}
engines: {node: '>= 16'}
'@intlify/message-resolver@9.1.9':
resolution: {integrity: sha512-Lx/DBpigeK0sz2BBbzv5mu9/dAlt98HxwbG7xLawC3O2xMF9MNWU5FtOziwYG6TDIjNq0O/3ZbOJAxwITIWXEA==}
engines: {node: '>= 10'}
@ -1419,14 +1419,14 @@ packages:
resolution: {integrity: sha512-XgPw8+UlHCiie3fI41HPVa/VDJb3/aSH7bLhY1hJvlvNV713PFtb4p4Jo+rlE0gAoMsMCGcsiT982fImolSltg==}
engines: {node: '>= 10'}
'@intlify/shared@11.0.0-rc.1':
resolution: {integrity: sha512-8tR1xe7ZEbkabTuE/tNhzpolygUn9OaYp9yuYAF4MgDNZg06C3Qny80bes2/e9/Wm3aVkPUlCw6WgU7mQd0yEg==}
engines: {node: '>= 16'}
'@intlify/shared@9.1.9':
resolution: {integrity: sha512-xKGM1d0EAxdDFCWedcYXOm6V5Pfw/TMudd6/qCdEb4tv0hk9EKeg7lwQF1azE0dP2phvx0yXxrt7UQK+IZjNdw==}
engines: {node: '>= 10'}
'@intlify/shared@9.13.1':
resolution: {integrity: sha512-u3b6BKGhE6j/JeRU6C/RL2FgyJfy6LakbtfeVF8fJXURpZZTzfh3e05J0bu0XPw447Q6/WUp3C4ajv4TMS4YsQ==}
engines: {node: '>= 16'}
'@intlify/vue-devtools@9.1.9':
resolution: {integrity: sha512-YPehH9uL4vZcGXky4Ev5qQIITnHKIvsD2GKGXgqf+05osMUI6WSEQHaN9USRa318Rs8RyyPCiDfmA0hRu3k7og==}
engines: {node: '>= 10'}
@ -4819,8 +4819,8 @@ packages:
'@vue/composition-api':
optional: true
vue-i18n@9.13.1:
resolution: {integrity: sha512-mh0GIxx0wPtPlcB1q4k277y0iKgo25xmDPWioVVYanjPufDBpvu5ySTjP5wOrSvlYQ2m1xI+CFhGdauv/61uQg==}
vue-i18n@11.0.0-rc.1:
resolution: {integrity: sha512-qbdCbA537HEdr2yXQ4ec/OMDsoHjod1DwnWbrf+l4Cu/O7CYTCKsOyITUm3RmrCJgRnoVycuR6i/JWdNTJvD5g==}
engines: {node: '>= 16'}
peerDependencies:
vue: ^3.0.0
@ -6730,6 +6730,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@intlify/core-base@11.0.0-rc.1':
dependencies:
'@intlify/message-compiler': 11.0.0-rc.1
'@intlify/shared': 11.0.0-rc.1
'@intlify/core-base@9.1.9':
dependencies:
'@intlify/devtools-if': 9.1.9
@ -6739,26 +6744,21 @@ snapshots:
'@intlify/shared': 9.1.9
'@intlify/vue-devtools': 9.1.9
'@intlify/core-base@9.13.1':
dependencies:
'@intlify/message-compiler': 9.13.1
'@intlify/shared': 9.13.1
'@intlify/devtools-if@9.1.9':
dependencies:
'@intlify/shared': 9.1.9
'@intlify/message-compiler@11.0.0-rc.1':
dependencies:
'@intlify/shared': 11.0.0-rc.1
source-map-js: 1.2.0
'@intlify/message-compiler@9.1.9':
dependencies:
'@intlify/message-resolver': 9.1.9
'@intlify/shared': 9.1.9
source-map: 0.6.1
'@intlify/message-compiler@9.13.1':
dependencies:
'@intlify/shared': 9.13.1
source-map-js: 1.2.0
'@intlify/message-resolver@9.1.9': {}
'@intlify/runtime@9.1.9':
@ -6767,9 +6767,9 @@ snapshots:
'@intlify/message-resolver': 9.1.9
'@intlify/shared': 9.1.9
'@intlify/shared@9.1.9': {}
'@intlify/shared@11.0.0-rc.1': {}
'@intlify/shared@9.13.1': {}
'@intlify/shared@9.1.9': {}
'@intlify/vue-devtools@9.1.9':
dependencies:
@ -10890,10 +10890,10 @@ snapshots:
dependencies:
vue: 3.4.35(typescript@5.5.4)
vue-i18n@9.13.1(vue@3.4.35(typescript@5.5.4)):
vue-i18n@11.0.0-rc.1(vue@3.4.35(typescript@5.5.4)):
dependencies:
'@intlify/core-base': 9.13.1
'@intlify/shared': 9.13.1
'@intlify/core-base': 11.0.0-rc.1
'@intlify/shared': 11.0.0-rc.1
'@vue/devtools-api': 6.6.3
vue: 3.4.35(typescript@5.5.4)

View File

@ -0,0 +1,70 @@
<template>
<span>
<template v-for="(part, index) in parts" :key="index">
<span v-if="part.highlighted" :class="highlightClass">
{{ part.text }}
</span>
<span v-else>{{ part.text }}</span>
</template>
</span>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
text: {
type: String,
required: true,
},
searchText: {
type: String,
default: '',
},
highlightClass: {
type: String,
default: 'highlight',
},
})
const escapedSearchText = computed(() =>
String(props.searchText).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'),
)
const pattern = computed(() => new RegExp(escapedSearchText.value, 'gi'))
const parts = computed(() => {
if (!props.searchText || !props.text)
return [{ text: props.text, highlighted: false }];
const result = [];
let currentIndex = 0;
const escapedSearchTextValue = escapedSearchText.value;
const searchPattern = new RegExp(`(${escapedSearchTextValue})`, 'gi');
props.text.replace(searchPattern, (match, p1, offset) => {
//
if (currentIndex < offset) {
result.push({ text: props.text.slice(currentIndex, offset), highlighted: false });
}
//
result.push({ text: p1, highlighted: true });
//
currentIndex = offset + p1.length;
return p1; // replace
});
//
if (currentIndex < props.text.length) {
result.push({ text: props.text.slice(currentIndex), highlighted: false });
}
return result;
});
</script>
<style scoped>
.highlight {
color: #7a58de;
}
</style>

View File

@ -5,17 +5,21 @@
</div>
<div class="result-info">
<div class="info-name">
<span class="text-[32rpx] font-medium">
{{ resultName }}
</span>
<HighlightText
class="text-[32rpx] font-medium"
:text="resultName"
:searchText="props.searchText"
/>
<div class="info-tag" v-if="resultType">
<span class="text-[24rpx] font-medium">{{ resultType }}</span>
</div>
</div>
<div class="info-detail" v-if="resultDetail">
<span class="text-[28rpx] font-regular">
{{ resultDetail }}
</span>
<HighlightText
class="text-[28rpx] font-regular"
:text="resultDetail"
:searchText="props.searchText"
/>
</div>
</div>
</div>
@ -24,109 +28,108 @@
import zu4992 from '@/static/image/chatList/zu4992@2x.png'
import zu4991 from '@/static/image/chatList/zu4991@2x.png'
import zu4989 from '@/static/image/chatList/zu4989@2x.png'
import {
ref,
watch,
computed,
onMounted,
onUnmounted,
reactive,
getCurrentInstance,
} from 'vue'
const { proxy } = getCurrentInstance()
import zu5296 from '@/static/image/chatList/zu5296@2x.png'
import { ref, watch, computed, onMounted, onUnmounted, reactive } from 'vue'
import HighlightText from './highLightText.vue'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const props = defineProps({
searchItem: Object | Number,
searchResultKey: String,
searchText: String,
})
// -
const keyMapping = {
user_infos: { avatar: 'avatar', name: 'nickname' },
group_infos: { avatar: 'avatar', name: 'name' },
group_member_infos: {
avatar: 'group_avatar',
name: 'group_name',
detailKey: 'user_name',
},
general_infos: {
avatar: 'receiver_avatar',
name: 'receiver_name',
detailKey: 'count',
},
}
//key
const getKeyValue = (keys) => {
let keyValue = ''
if (keys) {
keyValue = props.searchItem[keys]
}
return keyValue
}
//
const avatarImg = computed(() => {
let srcT = ''
switch (props.searchItem?.group_type) {
case 0:
srcT = zu5296
break
case 1:
srcT = zu4992
break
case 2:
srcT = zu4991
break
case 3:
srcT = zu4989
break
default:
srcT = zu4992
let avatar = getKeyValue(keyMapping[props.searchResultKey]?.avatar)
if (!avatar) {
avatar = groupTypeMapping[props.searchItem?.group_type]?.defaultImg
}
return srcT
return avatar
})
//
const resultName = computed(() => {
let data_key = ''
switch (props.searchResultKey) {
case 'user_infos':
data_key = props.searchItem?.nickname
break
case 'group_member_infos':
data_key = props.searchItem?.group_name
break
case 'general_infos':
data_key = props.searchItem?.receiver_name
break
default:
data_key = ''
}
return data_key
return getKeyValue(keyMapping[props.searchResultKey]?.name)
})
// -groupType
const groupTypeMapping = {
0: {},
1: {
defaultImg: zu4992,
},
2: {
result_type: t('index.mine.department'),
defaultImg: zu4989,
},
3: {
result_type: t('index.mine.project'),
defaultImg: zu4991,
},
4: {
defaultImg: zu5296,
},
}
//tag
const resultType = computed(() => {
let result_type = ''
switch (props.searchItem?.group_type) {
case 0:
result_type = ''
break
case 1:
result_type = ''
break
case 2:
result_type = proxy.$t('index.mine.department')
break
case 3:
result_type = proxy.$t('index.mine.project')
break
case 4:
result_type = ''
break
default:
result_type = ''
}
return result_type
return groupTypeMapping[props.searchItem?.group_type]?.result_type
})
//
const resultDetail = computed(() => {
let data_key = ''
switch (props.searchResultKey) {
case 'user_infos':
data_key = ''
let result_detail =
props.searchItem[keyMapping[props.searchResultKey]?.detailKey]
switch (keyMapping[props.searchResultKey]?.detailKey) {
case 'count':
result_detail = result_detail + t('search.chat.count')
break
case 'group_member_infos':
data_key = ''
break
case 'general_infos':
data_key = props.searchItem?.count + proxy.$t('search.chat.count')
case 'user_name':
result_detail = t('search.result.include') + result_detail
break
default:
data_key = ''
result_detail = ''
}
return data_key
return result_detail
})
</script>
<style lang="scss" scoped>
.search-item:nth-child(1) {
border-top: 1px solid $theme-border-color;
}
.search-item {
display: flex;
flex-direction: row;
align-items: center;
justify-content: flex-start;
padding: 22rpx 0 24rpx;
border-bottom: 1px solid $theme-border-color;
.avatar-img {
width: 96rpx;
height: 96rpx;
margin: 0 20rpx 0 0;
border-radius: 50%;
overflow: hidden;
img {
width: 100%;
height: 100%;

View File

@ -25,7 +25,7 @@
!state.searchText ? 'align-items:center;justify-content:center;' : ''
"
>
<div class="search-result-list">
<div class="search-result-list" v-if="state.searchText">
<div
class="search-result-each-part"
v-for="(searchResultValue,
@ -33,13 +33,16 @@
searchResultIndex) in state.searchResult"
:key="searchResultKey"
>
<div class="result-title"></div>
<div class="result-title text-[28rpx] font-regular">
{{ getResultKeysValue(searchResultKey) }}
</div>
<div class="result-list">
<searchItem
v-for="(item, index) in state?.searchResult[searchResultKey]"
:key="index"
:searchResultKey="searchResultKey"
:searchItem="item"
:searchText="state.searchText"
></searchItem>
</div>
<div class="result-has-more"></div>
@ -61,6 +64,8 @@ import searchItem from './components/searchItem.vue'
import { ServeSeachQueryAll } from '@/api/search/index'
import { ref, watch, computed, onMounted, onUnmounted, reactive } from 'vue'
import { useAuth } from '@/store/auth'
import { useI18n } from 'vue-i18n'
const { t } = useI18n()
const state = reactive({
searchText: '', //
searchResult: null, //
@ -74,6 +79,28 @@ const inputSearchText = (e) => {
queryAllSearch(searchText)
}
//key
const getResultKeysValue = (keys) => {
let resultKey = ''
switch (keys) {
case 'user_infos':
resultKey = t('index.mine.addressBook')
break
case 'group_infos':
resultKey = t('chat.type.group')
break
case 'group_member_infos':
resultKey = t('chat.type.group')
break
case 'general_infos':
resultKey = t('chat.type.record')
break
default:
resultKey = ''
}
return resultKey
}
// ES-
const queryAllSearch = (searchText) => {
let params = {
@ -152,7 +179,18 @@ page {
justify-content: flex-start;
.search-result-list {
width: 100%;
padding: 10rpx 18rpx;
.search-result-each-part {
margin: 46rpx 0 0;
.result-title {
line-height: 40rpx;
padding: 0 0 10rpx;
color: $theme-hint-text;
}
}
}
.search-no-result {

View File

@ -1,3 +1,4 @@
$theme-primary: #46299d;
$theme-text: #191919;
$theme-hint-text: #999999;
$theme-border-color: #f8f8f8;

View File

@ -76,5 +76,8 @@
"cancel": "取消",
"search.hint": "检索您要查找的内容吧~",
"search.chat.count": "条相关聊天记录",
"index.mine.project": "项目"
"index.mine.project": "项目",
"chat.type.group": "群聊",
"chat.type.record": "聊天记录",
"search.result.include": "包含:"
}