hookehuyr

feat: 新增搜索页面并完善首页搜索功能

- 创建搜索页面(/pages/search/index)
- 实现搜索输入框和清空功能
- 添加3个Tab切换:全部、产品、资料
- 实现搜索结果列表展示
- 添加空状态和初始状态UI
- 添加热门搜索推荐功能
- 首页搜索栏添加点击跳转
- 在 app.config.js 中注册新页面

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
7 */ 7 */
8 const pages = [ 8 const pages = [
9 'pages/index/index', 9 'pages/index/index',
10 + 'pages/search/index',
10 'pages/auth/index', 11 'pages/auth/index',
11 'pages/onboarding/index', 12 'pages/onboarding/index',
12 'pages/family-office/index', 13 'pages/family-office/index',
......
...@@ -11,7 +11,9 @@ ...@@ -11,7 +11,9 @@
11 11
12 <!-- Search Bar --> 12 <!-- Search Bar -->
13 <view 13 <view
14 - class="flex items-center w-full h-[88rpx] bg-white/20 backdrop-blur-md rounded-full px-[32rpx] border border-white/30"> 14 + class="flex items-center w-full h-[88rpx] bg-white/20 backdrop-blur-md rounded-full px-[32rpx] border border-white/30"
15 + @tap="go('/pages/search/index')"
16 + >
15 <IconFont name="Search" class="text-white/80 mr-[16rpx]" size="18" /> 17 <IconFont name="Search" class="text-white/80 mr-[16rpx]" size="18" />
16 <text class="text-white/80 text-[28rpx]">搜索培训资料、案例...</text> 18 <text class="text-white/80 text-[28rpx]">搜索培训资料、案例...</text>
17 </view> 19 </view>
......
1 +export default {
2 + navigationBarTitleText: '搜索',
3 + enablePullDownRefresh: true,
4 + backgroundColor: '#F9FAFB'
5 +}
1 +<template>
2 + <view class="min-h-screen bg-[#F9FAFB] pb-[calc(160rpx+env(safe-area-inset-bottom))]">
3 + <!-- Navigation Header -->
4 + <NavHeader title="搜索" />
5 +
6 + <!-- Content Area -->
7 + <view class="px-[40rpx] mt-[40rpx]">
8 + <!-- Search Input -->
9 + <view class="flex items-center w-full h-[88rpx] bg-white rounded-full px-[32rpx] border border-gray-200 mb-[40rpx]">
10 + <IconFont name="Search" class="text-gray-400 mr-[16rpx]" size="18" />
11 + <input
12 + v-model="searchKeyword"
13 + type="text"
14 + placeholder="搜索培训资料、案例、产品..."
15 + class="flex-1 text-[28rpx] text-gray-800 placeholder-gray-400"
16 + @confirm="handleSearch"
17 + />
18 + <view v-if="searchKeyword" class="ml-[16rpx]" @tap="clearSearch">
19 + <IconFont name="Close" class="text-gray-400" size="16" />
20 + </view>
21 + </view>
22 +
23 + <!-- Filter Tabs -->
24 + <view class="flex overflow-x-auto no-scrollbar mb-[40rpx] space-x-[24rpx]">
25 + <view v-for="(tab, index) in tabs" :key="index"
26 + class="px-[32rpx] py-[16rpx] rounded-full text-[28rpx] whitespace-nowrap transition-colors"
27 + :class="activeTab === index ? 'bg-[#2563EB] text-white' : 'bg-[#F3F4F6] text-[#6B7280]'"
28 + @tap="activeTab = index">
29 + {{ tab }}
30 + </view>
31 + </view>
32 +
33 + <!-- Search Results -->
34 + <view v-if="searchResults.length > 0">
35 + <!-- Result Count -->
36 + <view class="text-[#6B7280] text-[24rpx] mb-[24rpx]">
37 + 找到 {{ searchResults.length }} 个相关结果
38 + </view>
39 +
40 + <!-- Results List -->
41 + <view class="flex flex-col gap-[24rpx]">
42 + <!-- Product Card -->
43 + <view
44 + v-for="(item, index) in searchResults"
45 + :key="index"
46 + class="bg-white rounded-[24rpx] overflow-hidden shadow-sm"
47 + @tap="goToDetail(item)"
48 + >
49 + <!-- Image + Content Layout -->
50 + <view class="flex gap-[24rpx] p-[24rpx]">
51 + <!-- Image -->
52 + <image
53 + class="w-[200rpx] h-[140rpx] rounded-[16rpx] bg-gray-100 flex-shrink-0"
54 + :src="item.image"
55 + mode="aspectFill"
56 + />
57 +
58 + <!-- Content -->
59 + <view class="flex-1 flex flex-col justify-between py-[4rpx]">
60 + <!-- Title -->
61 + <view class="text-[#1F2937] text-[28rpx] font-medium leading-[1.4] line-clamp-2">
62 + {{ item.title }}
63 + </view>
64 +
65 + <!-- Meta Info -->
66 + <view class="flex justify-between items-center">
67 + <view class="flex gap-[12rpx]">
68 + <!-- Type Tag -->
69 + <view
70 + class="px-[12rpx] py-[4rpx] rounded-[8rpx] text-[22rpx]"
71 + :class="item.type === '产品' ? 'bg-blue-50 text-blue-600' : 'bg-green-50 text-green-600'"
72 + >
73 + {{ item.type }}
74 + </view>
75 + <!-- Hot Tag -->
76 + <view v-if="item.tag" class="bg-red-50 text-red-600 text-[22rpx] px-[12rpx] py-[4rpx] rounded-[8rpx]">
77 + {{ item.tag }}
78 + </view>
79 + </view>
80 + <view class="text-[#6B7280] text-[24rpx]">
81 + {{ item.views || 0 }}人查看
82 + </view>
83 + </view>
84 + </view>
85 + </view>
86 + </view>
87 + </view>
88 + </view>
89 +
90 + <!-- Empty State -->
91 + <view v-else-if="hasSearched" class="flex flex-col items-center justify-center py-[120rpx]">
92 + <image
93 + class="w-[320rpx] h-[320rpx] mb-[40rpx]"
94 + src="https://picsum.photos/seed/empty/320/320"
95 + mode="aspectFit"
96 + />
97 + <view class="text-[#6B7280] text-[28rpx]">暂无搜索结果</view>
98 + <view class="text-[#9CA3AF] text-[24rpx] mt-[12rpx]">试试其他关键词吧</view>
99 + </view>
100 +
101 + <!-- Initial State -->
102 + <view v-else class="flex flex-col items-center justify-center py-[120rpx]">
103 + <IconFont name="Search" class="text-gray-300 mb-[24rpx]" size="64" />
104 + <view class="text-[#6B7280] text-[28rpx]">搜索培训资料、案例、产品</view>
105 + <view class="text-[#9CA3AF] text-[24rpx] mt-[12rpx]">输入关键词开始搜索</view>
106 + </view>
107 +
108 + <!-- Hot Searches -->
109 + <view v-if="!hasSearched" class="mt-[60rpx]">
110 + <view class="text-[#1F2937] text-[28rpx] font-bold mb-[24rpx]">热门搜索</view>
111 + <view class="flex flex-wrap gap-[16rpx]">
112 + <view
113 + v-for="(keyword, index) in hotSearches"
114 + :key="index"
115 + class="px-[24rpx] py-[12rpx] bg-white rounded-full text-[26rpx] text-[#4B5563]"
116 + @tap="searchKeyword = keyword; handleSearch()"
117 + >
118 + {{ keyword }}
119 + </view>
120 + </view>
121 + </view>
122 + </view>
123 +
124 + <!-- Tab Bar -->
125 + <TabBar current="home" />
126 + </view>
127 +</template>
128 +
129 +<script setup>
130 +import { ref, computed } from 'vue'
131 +import Taro from '@tarojs/taro'
132 +import { useGo } from '@/hooks/useGo'
133 +import NavHeader from '@/components/NavHeader.vue'
134 +import TabBar from '@/components/TabBar.vue'
135 +import IconFont from '@/components/IconFont.vue'
136 +
137 +// Navigation
138 +const go = useGo()
139 +
140 +// State
141 +const searchKeyword = ref('')
142 +const activeTab = ref(0)
143 +const hasSearched = ref(false)
144 +
145 +// Tabs
146 +const tabs = ['全部', '产品', '资料']
147 +
148 +// Hot searches
149 +const hotSearches = ref([
150 + '家庭财富传承',
151 + '儿童教育金',
152 + '医疗保险',
153 + '高净值客户',
154 + '保险合同',
155 +])
156 +
157 +// Mock data
158 +const mockData = ref([
159 + {
160 + id: 1,
161 + title: '家庭财富传承保障计划(分红)',
162 + type: '产品',
163 + tag: '热卖',
164 + views: 256,
165 + image: 'https://picsum.photos/seed/prod1/200/140',
166 + category: 'product'
167 + },
168 + {
169 + id: 2,
170 + title: '2024年保险市场趋势分析报告',
171 + type: '资料',
172 + views: 189,
173 + image: 'https://picsum.photos/seed/mat1/200/140',
174 + category: 'material'
175 + },
176 + {
177 + id: 3,
178 + title: '儿童教育金储备方案(分红)',
179 + type: '产品',
180 + tag: '推荐',
181 + views: 342,
182 + image: 'https://picsum.photos/seed/prod2/200/140',
183 + category: 'product'
184 + },
185 + {
186 + id: 4,
187 + title: '高净值客户需求分析与产品匹配',
188 + type: '资料',
189 + views: 142,
190 + image: 'https://picsum.photos/seed/mat2/200/140',
191 + category: 'material'
192 + },
193 + {
194 + id: 5,
195 + title: '百万医疗保险计划',
196 + type: '产品',
197 + views: 267,
198 + image: 'https://picsum.photos/seed/prod3/200/140',
199 + category: 'product'
200 + },
201 + {
202 + id: 6,
203 + title: '保险合同条款解读与风险提示',
204 + type: '资料',
205 + views: 198,
206 + image: 'https://picsum.photos/seed/mat3/200/140',
207 + category: 'material'
208 + },
209 + {
210 + id: 7,
211 + title: '意外伤害保障计划',
212 + type: '产品',
213 + tag: '热卖',
214 + views: 223,
215 + image: 'https://picsum.photos/seed/prod4/200/140',
216 + category: 'product'
217 + },
218 + {
219 + id: 8,
220 + title: '保险销售实战技巧分享',
221 + type: '资料',
222 + views: 156,
223 + image: 'https://picsum.photos/seed/mat4/200/140',
224 + category: 'material'
225 + },
226 +])
227 +
228 +// Search results
229 +const searchResults = computed(() => {
230 + if (!hasSearched.value) return []
231 +
232 + let results = mockData.value
233 +
234 + // Filter by tab
235 + if (activeTab.value === 1) {
236 + results = results.filter(item => item.category === 'product')
237 + } else if (activeTab.value === 2) {
238 + results = results.filter(item => item.category === 'material')
239 + }
240 +
241 + // Filter by keyword
242 + if (searchKeyword.value.trim()) {
243 + const keyword = searchKeyword.value.toLowerCase()
244 + results = results.filter(item =>
245 + item.title.toLowerCase().includes(keyword)
246 + )
247 + }
248 +
249 + return results
250 +})
251 +
252 +// Handle search
253 +const handleSearch = () => {
254 + if (searchKeyword.value.trim()) {
255 + hasSearched.value = true
256 + }
257 +}
258 +
259 +// Clear search
260 +const clearSearch = () => {
261 + searchKeyword.value = ''
262 + hasSearched.value = false
263 +}
264 +
265 +// Go to detail
266 +const goToDetail = (item) => {
267 + if (item.category === 'product') {
268 + go('/pages/knowledge-base/index')
269 + } else {
270 + go('/pages/knowledge-base/index')
271 + }
272 +
273 + Taro.showToast({
274 + title: `查看${item.type}详情`,
275 + icon: 'none',
276 + duration: 1500
277 + })
278 +}
279 +</script>
280 +
281 +<style>
282 +.no-scrollbar::-webkit-scrollbar {
283 + display: none;
284 +}
285 +
286 +.no-scrollbar {
287 + -ms-overflow-style: none;
288 + scrollbar-width: none;
289 +}
290 +</style>