Toggle navigation
Toggle navigation
This project
Loading...
Sign in
Hooke
/
manulife-weapp
Go to a project
Toggle navigation
Toggle navigation pinning
Projects
Groups
Snippets
Help
Project
Activity
Repository
Graphs
Network
Create a new issue
Commits
Issue Boards
Authored by
hookehuyr
2026-02-05 21:12:48 +0800
Browse Files
Options
Browse Files
Download
Email Patches
Plain Diff
Commit
6c95ffcdaff12d918b86d10b738d4e9539e25215
6c95ffcd
1 parent
647655bf
feat(material): 优化搜索功能体验
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
156 additions
and
21 deletions
components.d.ts
docs/CHANGELOG.md
src/components/SearchBar.vue
src/pages/category-list/index.vue
src/pages/help-center/index.vue
src/pages/knowledge-base/index.vue
src/pages/material-list/index.vue
src/pages/message/index.vue
src/utils/debounce.js
components.d.ts
View file @
6c95ffc
...
...
@@ -27,7 +27,7 @@ declare module 'vue' {
OfficeViewer
:
typeof
import
(
'./src/components/OfficeViewer.vue'
)[
'default'
]
PdfPreview
:
typeof
import
(
'./src/components/PdfPreview.vue'
)[
'default'
]
Picker
:
typeof
import
(
'./src/components/time-picker-data/picker.vue'
)[
'default'
]
PlanPopup
:
typeof
import
(
'./src/components/Plan
Popup/index
.vue'
)[
'default'
]
PlanPopup
:
typeof
import
(
'./src/components/Plan
Schemes/PlanPopup
.vue'
)[
'default'
]
PosterBuilder
:
typeof
import
(
'./src/components/PosterBuilder/index.vue'
)[
'default'
]
QrCode
:
typeof
import
(
'./src/components/qrCode.vue'
)[
'default'
]
QrCodeSearch
:
typeof
import
(
'./src/components/qrCodeSearch.vue'
)[
'default'
]
...
...
docs/CHANGELOG.md
View file @
6c95ffc
# Changelog
> 本文档记录 Manulife WeApp
项目的所有重要变更。
> 本文档记录 Manulife WeApp项目的所有重要变更。
> 格式基于 [Keep a Changelog](https://keepachangelog.com/zh-CN/1.0.0/),
---
## [2026-02-05] - 优化搜索功能体验
### 新增
-
帮助中心页面实现实时搜索功能(模糊匹配问题标题)
-
新增防抖工具函数 (src/utils/debounce.js)
-
资料列表页面改造为实时搜索(带 500ms 防抖)
### 优化
-
SearchBar 组件修复 blur 事件传递参数
-
移除资料列表页面冗余的 blur 事件监听
-
页面空状态 UI 优化(category-list, message)
### 技术实现
-
help-center: 使用 v-model + computed 实现纯前端过滤
-
material-list: 使用 watch + debounce 实现防抖接口调用
-
SearchBar: blur 事件现在传递当前输入值
---
**详细信息**
:
-
**影响文件**
:
-
src/components/SearchBar.vue
-
src/utils/debounce.js (新增)
-
src/pages/help-center/index.vue
-
src/pages/material-list/index.vue
-
src/pages/category-list/index.vue
-
src/pages/knowledge-base/index.vue
-
src/pages/message/index.vue
-
**技术栈**
: Vue 3, Taro 4, Watch API
-
**测试状态**
: 已通过本地测试
-
**备注**
: 所有搜索功能已统一为实时搜索模式
---
## [2026-02-05] - 修复知识库搜索栏高度变化
### 修复
...
...
src/components/SearchBar.vue
View file @
6c95ffc
...
...
@@ -168,8 +168,9 @@ const emit = defineEmits({
/**
* 失去焦点
* @event blur
* @param {string} value - 当前输入值
*/
blur: () => true,
blur: (
value
) => true,
/**
* 清除
* @event clear
...
...
@@ -219,7 +220,7 @@ function handleFocus() {
*/
function handleBlur() {
console.log('[SearchBar Component] 失去焦点')
emit('blur')
emit('blur'
, internalValue.value
)
}
/**
...
...
src/pages/category-list/index.vue
View file @
6c95ffc
...
...
@@ -21,7 +21,8 @@
<!-- Empty State -->
<div v-else class="px-[40rpx] mt-[80rpx] flex items-center justify-center">
<text class="text-[#9CA3AF] text-[28rpx]">暂无分类</text>
<!-- <text class="text-[#9CA3AF] text-[28rpx]">暂无分类</text> -->
<nut-empty description="暂无分类" image="empty" />
</div>
</div>
</template>
...
...
src/pages/help-center/index.vue
View file @
6c95ffc
...
...
@@ -7,8 +7,10 @@
<!-- Search Bar -->
<view class="mb-[32rpx]">
<SearchBar
v-model="searchKeyword"
placeholder="搜索问题或关键词"
variant="rounded"
:show-clear="true"
/>
</view>
...
...
@@ -32,14 +34,19 @@
<view>
<text class="block text-[32rpx] text-gray-900 font-bold mb-[24rpx]">重点问题</text>
<view class="bg-white rounded-[24rpx] shadow-sm overflow-hidden">
<view v-for="(item, index) in questions" :key="index"
class="flex items-center justify-between p-[32rpx] border-b border-gray-100 last:border-b-0 active:bg-gray-50 transition-colors"
@tap="handleQuestionClick(item)">
<view class="flex items-center">
<view class="w-[12rpx] h-[12rpx] rounded-full bg-blue-600 mr-[24rpx]"></view>
<text class="text-[28rpx] text-gray-800">{{ item.title }}</text>
<view v-if="filteredQuestions.length > 0">
<view v-for="(item, index) in filteredQuestions" :key="index"
class="flex items-center justify-between p-[32rpx] border-b border-gray-100 last:border-b-0 active:bg-gray-50 transition-colors"
@tap="handleQuestionClick(item)">
<view class="flex items-center">
<view class="w-[12rpx] h-[12rpx] rounded-full bg-blue-600 mr-[24rpx]"></view>
<text class="text-[28rpx] text-gray-800">{{ item.title }}</text>
</view>
<IconFont name="rectRight" class="text-gray-400" size="16" />
</view>
<IconFont name="rectRight" class="text-gray-400" size="16" />
</view>
<view v-else class="p-[64rpx] text-center">
<text class="text-[28rpx] text-gray-400">未找到相关问题</text>
</view>
</view>
</view>
...
...
@@ -118,7 +125,7 @@
</template>
<script setup>
import { ref } from 'vue'
import { ref
, computed
} from 'vue'
import NavHeader from '@/components/NavHeader.vue'
import TabBar from '@/components/TabBar.vue'
import IconFont from '@/components/IconFont.vue'
...
...
@@ -129,6 +136,9 @@ const showContactPopup = ref(false)
const showQuestionPopup = ref(false)
const currentQuestion = ref(null)
// 搜索关键字
const searchKeyword = ref('')
// Contact methods data
const contactMethods = [
{
...
...
@@ -225,6 +235,17 @@ const questions = ref([
}
])
// 模糊匹配过滤问题列表
const filteredQuestions = computed(() => {
if (!searchKeyword.value) {
return questions.value
}
const keyword = searchKeyword.value.toLowerCase()
return questions.value.filter(q =>
q.title.toLowerCase().includes(keyword)
)
})
const handleQuestionClick = (item) => {
currentQuestion.value = item
showQuestionPopup.value = true
...
...
src/pages/knowledge-base/index.vue
View file @
6c95ffc
...
...
@@ -103,7 +103,7 @@
<!-- 没有更多数据 -->
<view v-if="!hasMore && products.length > 0" class="flex justify-center items-center py-[40rpx]">
<text class="text-gray-400 text-[2
8
rpx]">没有更多了</text>
<text class="text-gray-400 text-[2
4
rpx]">没有更多了</text>
</view>
<!-- 空状态 -->
...
...
src/pages/material-list/index.vue
View file @
6c95ffc
...
...
@@ -12,7 +12,6 @@
v-model="searchValue"
placeholder="搜索资料..."
@search="onSearch"
@blur="onSearch"
@clear="onClear"
variant="rounded"
:show-border="true"
...
...
@@ -117,13 +116,14 @@
</template>
<script setup>
import { ref, computed, nextTick } from 'vue'
import { ref, computed, nextTick
, watch
} from 'vue'
import { useLoad, useReachBottom } from '@tarojs/taro'
import NavHeader from '@/components/NavHeader.vue'
import SearchBar from '@/components/SearchBar.vue'
import ListItemActions from '@/components/ListItemActions/index.vue'
import { useListItemClick, ListType } from '@/composables/useListItemClick'
import { getDocumentIcon, getDocumentLabel } from '@/utils/documentIcons'
import { debounce } from '@/utils/debounce'
import { fileListAPI } from '@/api/file'
import { useCollectOperation } from '@/composables/useCollectOperation'
import Taro from '@tarojs/taro'
...
...
@@ -134,6 +134,15 @@ const listVisible = ref(true)
const listRenderKey = ref(0)
/**
* 防抖搜索函数
* @description 使用防抖优化搜索性能,避免频繁请求接口
*/
const debouncedSearch = debounce(async () => {
console.log('[Material List] 防抖搜索触发')
await onSearch()
}, 500)
/**
* 加载状态
*/
const loading = ref(false)
...
...
@@ -601,6 +610,25 @@ const onSearch = async () => {
}
/**
* 监听搜索关键字变化
* @description 实现实时搜索:用户输入时自动触发搜索(带防抖)
*/
watch(searchValue, (newValue, oldValue) => {
console.log('[Material List] searchValue 变化:', oldValue, '->', newValue)
// 如果搜索关键字为空,立即清除搜索(不需要防抖)
if (!newValue?.trim()) {
console.log('[Material List] 搜索关键字为空,立即清除')
onClear()
return
}
// 有搜索关键字,使用防抖搜索
console.log('[Material List] 触发防抖搜索')
debouncedSearch()
})
/**
* 清除搜索关键词
* @description 用户点击搜索框右侧的删除按钮时触发,重新请求当前分类的最新数据
*
...
...
src/pages/message/index.vue
View file @
6c95ffc
...
...
@@ -21,14 +21,14 @@
{{ item.create_time }}
</text>
</view>
<view class="text-sm text-gray-600 line-clamp-2 leading-relaxed">
{{ item.intro || item.content || '暂无简介' }}
</view>
</view>
<!-- 加载更多/没有更多 -->
<view class="py-4 text-center text-
xs
text-gray-400">
<view class="py-4 text-center text-
[24rpx]
text-gray-400">
<text v-if="loading">加载中...</text>
<text v-else-if="!hasMore">没有更多了</text>
<text v-else>上拉加载更多</text>
...
...
@@ -66,7 +66,7 @@ const loading = ref(false)
*/
const fetchMessageList = async (refresh = false) => {
if (loading.value) return
if (refresh) {
page.value = 1
hasMore.value = true
...
...
@@ -75,7 +75,7 @@ const fetchMessageList = async (refresh = false) => {
}
loading.value = true
try {
const res = await myListAPI({
page: page.value,
...
...
@@ -84,7 +84,7 @@ const fetchMessageList = async (refresh = false) => {
if (res.code === 1) {
const list = res.data?.list || []
if (refresh) {
messageList.value = list
} else {
...
...
src/utils/debounce.js
0 → 100644
View file @
6c95ffc
/**
* 防抖工具函数
*
* @description 创建防抖函数,延迟执行 func
* @param {Function} func - 需要防抖的函数
* @param {number} delay - 延迟时间(毫秒)
* @returns {Function} 防抖后的函数
*
* @example
* const debouncedSearch = debounce(() => {
* console.log('搜索')
* }, 500)
*
* debouncedSearch() // 等待 500ms 后执行
* debouncedSearch() // 取消上一次,重新计时
*/
export
function
debounce
(
func
,
delay
=
500
)
{
let
timer
=
null
return
function
(...
args
)
{
// 清除之前的定时器
if
(
timer
)
{
clearTimeout
(
timer
)
}
// 设置新的定时器
timer
=
setTimeout
(()
=>
{
func
.
apply
(
this
,
args
)
},
delay
)
}
}
/**
* 防抖 Hook
*
* @description 创建响应式防抖函数
* @param {Function} func - 需要防抖的函数
* @param {number} delay - 延迟时间(毫秒)
* @returns {Function} 防抖后的函数
*
* @example
* import { debounceFn } from '@/utils/debounce'
*
* const debouncedSearch = debounceFn(async () => {
* await fetchData()
* }, 500)
*/
export
function
debounceFn
(
func
,
delay
=
500
)
{
return
debounce
(
func
,
delay
)
}
Please
register
or
login
to post a comment