hookehuyr

feat: 知识库页面新增搜索功能

新增功能:
- 添加 SearchBar 搜索组件
- 实现实时搜索功能(带 500ms 防抖优化)
- 支持按回车键立即搜索
- 支持一键清空搜索内容
- 搜索与分类筛选联动(可组合使用)

优化体验:
- 输入时实时搜索(防抖延迟 500ms)
- 按回车键立即搜索(取消防抖定时器)
- 清空搜索时恢复当前分类的所有产品
- 切换分类时保持搜索状态

技术实现:
- API 接口:使用 listAPI 的 keyword 参数
- 防抖逻辑:使用 setTimeout 实现搜索防抖
- 状态管理:新增 searchValue 搜索状态
- 样式布局:参考 plan 页面的搜索栏布局

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -27,7 +27,7 @@ declare module 'vue' { ...@@ -27,7 +27,7 @@ declare module 'vue' {
27 OfficeViewer: typeof import('./src/components/OfficeViewer.vue')['default'] 27 OfficeViewer: typeof import('./src/components/OfficeViewer.vue')['default']
28 PdfPreview: typeof import('./src/components/PdfPreview.vue')['default'] 28 PdfPreview: typeof import('./src/components/PdfPreview.vue')['default']
29 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default'] 29 Picker: typeof import('./src/components/time-picker-data/picker.vue')['default']
30 - PlanPopup: typeof import('./src/components/PlanPopup/index.vue')['default'] 30 + PlanPopup: typeof import('./src/components/PlanSchemes/PlanPopup.vue')['default']
31 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] 31 PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default']
32 QrCode: typeof import('./src/components/qrCode.vue')['default'] 32 QrCode: typeof import('./src/components/qrCode.vue')['default']
33 QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default'] 33 QrCodeSearch: typeof import('./src/components/qrCodeSearch.vue')['default']
......
...@@ -5,6 +5,31 @@ ...@@ -5,6 +5,31 @@
5 5
6 --- 6 ---
7 7
8 +## [2026-02-04] - 知识库页面新增搜索功能
9 +
10 +### 新增
11 +- 知识库页面(`src/pages/knowledge-base/index.vue`
12 + - 添加 SearchBar 搜索组件
13 + - 实现实时搜索功能(带 500ms 防抖优化)
14 + - 支持按回车键立即搜索
15 + - 支持一键清空搜索内容
16 + - 搜索与分类筛选联动(可组合使用)
17 +
18 +### 优化
19 +- 搜索体验优化
20 + - 输入时实时搜索(防抖延迟 500ms)
21 + - 按回车键立即搜索(取消防抖定时器)
22 + - 清空搜索时恢复当前分类的所有产品
23 + - 切换分类时保持搜索状态
24 +
25 +### 技术实现
26 +- API 接口:使用 `listAPI``keyword` 参数
27 +- 防抖逻辑:使用 `setTimeout` 实现搜索防抖
28 +- 状态管理:新增 `searchValue` 搜索状态
29 +- 样式布局:参考 plan 页面的搜索栏布局
30 +
31 +---
32 +
8 ## [2026-02-04] - 清理 Apifox 依赖并优化工具链 33 ## [2026-02-04] - 清理 Apifox 依赖并优化工具链
9 34
10 ### 移除 35 ### 移除
......
...@@ -70,6 +70,12 @@ paths: ...@@ -70,6 +70,12 @@ paths:
70 required: false 70 required: false
71 schema: 71 schema:
72 type: string 72 type: string
73 + - name: keyword
74 + in: query
75 + description: 搜索
76 + required: false
77 + schema:
78 + type: string
73 responses: 79 responses:
74 '200': 80 '200':
75 description: '' 81 description: ''
...@@ -257,7 +263,7 @@ paths: ...@@ -257,7 +263,7 @@ paths:
257 x-apifox-ordering: 0 263 x-apifox-ordering: 0
258 security: [] 264 security: []
259 x-apifox-folder: 产品 265 x-apifox-folder: 产品
260 - x-apifox-status: done 266 + x-apifox-status: released
261 x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-414404531-run 267 x-run-in-apifox: https://app.apifox.com/web/project/7792797/apis/api-414404531-run
262 components: 268 components:
263 schemas: {} 269 schemas: {}
......
...@@ -56,6 +56,7 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params)); ...@@ -56,6 +56,7 @@ export const detailAPI = (params) => fn(fetch.get(Api.Detail, params));
56 * @param {string} params.page (可选) 56 * @param {string} params.page (可选)
57 * @param {string} params.cid (可选) 分类id 57 * @param {string} params.cid (可选) 分类id
58 * @param {string} params.recommend (可选) 推荐位: normal-普通, hot-热卖 58 * @param {string} params.recommend (可选) 推荐位: normal-普通, hot-热卖
59 + * @param {string} params.keyword (可选) 搜索
59 * @returns {Promise<{ 60 * @returns {Promise<{
60 * code: number; // 状态码 61 * code: number; // 状态码
61 * msg: string; // 消息 62 * msg: string; // 消息
......
1 <!-- 1 <!--
2 * @Date: 2026-01-31 2 * @Date: 2026-01-31
3 - * @Description: 产品知识库 - API 接口集成版本 3 + * @Description: 产品知识库 - API 接口集成版本(含搜索功能)
4 --> 4 -->
5 <template> 5 <template>
6 <view class="h-screen bg-[#F9FAFB] flex flex-col"> 6 <view class="h-screen bg-[#F9FAFB] flex flex-col">
7 <view class="bg-[#F9FAFB] z-10"> 7 <view class="bg-[#F9FAFB] z-10">
8 <NavHeader title="产品知识库" /> 8 <NavHeader title="产品知识库" />
9 9
10 + <!-- Search Bar -->
11 + <view class="px-[24rpx] py-[16rpx] bg-white">
12 + <SearchBar
13 + v-model="searchValue"
14 + placeholder="搜索产品名称..."
15 + :show-clear="true"
16 + @search="onSearch"
17 + @input="onSearchInput"
18 + @clear="onClear"
19 + />
20 + </view>
21 +
10 <!-- Tabs Container --> 22 <!-- Tabs Container -->
11 - <view class="flex-1 min-h-0 flex flex-col"> 23 + <view class="bg-white mt-[2rpx]">
12 <nut-tabs v-model="activeTabId"> 24 <nut-tabs v-model="activeTabId">
13 <!-- 自定义标签栏 --> 25 <!-- 自定义标签栏 -->
14 <template #titles> 26 <template #titles>
...@@ -105,11 +117,17 @@ ...@@ -105,11 +117,17 @@
105 import { ref, computed } from 'vue' 117 import { ref, computed } from 'vue'
106 import Taro, { useLoad, useReachBottom } from '@tarojs/taro' 118 import Taro, { useLoad, useReachBottom } from '@tarojs/taro'
107 import NavHeader from '@/components/NavHeader.vue' 119 import NavHeader from '@/components/NavHeader.vue'
120 +import SearchBar from '@/components/SearchBar.vue'
108 import { useListItemClick, ListType } from '@/composables/useListItemClick' 121 import { useListItemClick, ListType } from '@/composables/useListItemClick'
109 import { listAPI } from '@/api/get_product' 122 import { listAPI } from '@/api/get_product'
110 123
111 const activeTabId = ref('') 124 const activeTabId = ref('')
112 125
126 +// 搜索状态
127 +const searchValue = ref('')
128 +// 搜索防抖定时器
129 +let searchTimer = null
130 +
113 // 分页状态 131 // 分页状态
114 const page = ref(0) 132 const page = ref(0)
115 const limit = ref(10) 133 const limit = ref(10)
...@@ -136,7 +154,7 @@ const tabsData = computed(() => { ...@@ -136,7 +154,7 @@ const tabsData = computed(() => {
136 154
137 /** 155 /**
138 * 获取产品列表 156 * 获取产品列表
139 - * @description 根据 activeTabId 获取对应分类的产品列表 157 + * @description 根据 activeTabId 和 searchValue 获取对应分类的产品列表
140 */ 158 */
141 const fetchProducts = async (isLoadMore = false) => { 159 const fetchProducts = async (isLoadMore = false) => {
142 if (loading.value) return 160 if (loading.value) return
...@@ -154,6 +172,11 @@ const fetchProducts = async (isLoadMore = false) => { ...@@ -154,6 +172,11 @@ const fetchProducts = async (isLoadMore = false) => {
154 params.cid = activeTabId.value 172 params.cid = activeTabId.value
155 } 173 }
156 174
175 + // 添加搜索关键词参数
176 + if (searchValue.value) {
177 + params.keyword = searchValue.value
178 + }
179 +
157 const res = await listAPI(params) 180 const res = await listAPI(params)
158 181
159 if (res.code === 1 && res.data) { 182 if (res.code === 1 && res.data) {
...@@ -205,6 +228,76 @@ const onTabClick = (id) => { ...@@ -205,6 +228,76 @@ const onTabClick = (id) => {
205 products.value = [] 228 products.value = []
206 hasMore.value = true 229 hasMore.value = true
207 230
231 + // 重新加载数据(保持搜索状态)
232 + fetchProducts(false)
233 +}
234 +
235 +/**
236 + * 搜索输入处理(带防抖)
237 + * @description 用户输入时实时搜索,使用防抖优化性能
238 + * @param {string} value - 搜索关键词
239 + */
240 +const onSearchInput = (value) => {
241 + console.log('搜索输入:', value)
242 +
243 + // 清除之前的定时器
244 + if (searchTimer) {
245 + clearTimeout(searchTimer)
246 + }
247 +
248 + // 设置新的定时器(500ms 后执行搜索)
249 + searchTimer = setTimeout(() => {
250 + // 重置分页状态
251 + page.value = 0
252 + products.value = []
253 + hasMore.value = true
254 +
255 + // 重新加载数据
256 + fetchProducts(false)
257 + }, 500)
258 +}
259 +
260 +/**
261 + * 搜索处理(回车键)
262 + * @description 用户按下回车或点击搜索按钮时触发
263 + * @param {string} value - 搜索关键词
264 + */
265 +const onSearch = (value) => {
266 + console.log('搜索产品:', value)
267 +
268 + // 清除防抖定时器
269 + if (searchTimer) {
270 + clearTimeout(searchTimer)
271 + searchTimer = null
272 + }
273 +
274 + // 重置分页状态
275 + page.value = 0
276 + products.value = []
277 + hasMore.value = true
278 +
279 + // 重新加载数据
280 + fetchProducts(false)
281 +}
282 +
283 +/**
284 + * 清空搜索
285 + * @description 用户点击清除按钮时触发
286 + */
287 +const onClear = () => {
288 + console.log('清空搜索')
289 +
290 + // 清除防抖定时器
291 + if (searchTimer) {
292 + clearTimeout(searchTimer)
293 + searchTimer = null
294 + }
295 +
296 + // 重置分页状态
297 + page.value = 0
298 + products.value = []
299 + hasMore.value = true
300 +
208 // 重新加载数据 301 // 重新加载数据
209 fetchProducts(false) 302 fetchProducts(false)
210 } 303 }
......