feat: 知识库页面新增搜索功能
新增功能: - 添加 SearchBar 搜索组件 - 实现实时搜索功能(带 500ms 防抖优化) - 支持按回车键立即搜索 - 支持一键清空搜索内容 - 搜索与分类筛选联动(可组合使用) 优化体验: - 输入时实时搜索(防抖延迟 500ms) - 按回车键立即搜索(取消防抖定时器) - 清空搜索时恢复当前分类的所有产品 - 切换分类时保持搜索状态 技术实现: - API 接口:使用 listAPI 的 keyword 参数 - 防抖逻辑:使用 setTimeout 实现搜索防抖 - 状态管理:新增 searchValue 搜索状态 - 样式布局:参考 plan 页面的搜索栏布局 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Showing
5 changed files
with
130 additions
and
5 deletions
| ... | @@ -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 | } | ... | ... |
-
Please register or login to post a comment