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 @@
*/
const pages = [
'pages/index/index',
'pages/search/index',
'pages/auth/index',
'pages/onboarding/index',
'pages/family-office/index',
......
......@@ -11,7 +11,9 @@
<!-- Search Bar -->
<view
class="flex items-center w-full h-[88rpx] bg-white/20 backdrop-blur-md rounded-full px-[32rpx] border border-white/30">
class="flex items-center w-full h-[88rpx] bg-white/20 backdrop-blur-md rounded-full px-[32rpx] border border-white/30"
@tap="go('/pages/search/index')"
>
<IconFont name="Search" class="text-white/80 mr-[16rpx]" size="18" />
<text class="text-white/80 text-[28rpx]">搜索培训资料、案例...</text>
</view>
......
export default {
navigationBarTitleText: '搜索',
enablePullDownRefresh: true,
backgroundColor: '#F9FAFB'
}
<template>
<view class="min-h-screen bg-[#F9FAFB] pb-[calc(160rpx+env(safe-area-inset-bottom))]">
<!-- Navigation Header -->
<NavHeader title="搜索" />
<!-- Content Area -->
<view class="px-[40rpx] mt-[40rpx]">
<!-- Search Input -->
<view class="flex items-center w-full h-[88rpx] bg-white rounded-full px-[32rpx] border border-gray-200 mb-[40rpx]">
<IconFont name="Search" class="text-gray-400 mr-[16rpx]" size="18" />
<input
v-model="searchKeyword"
type="text"
placeholder="搜索培训资料、案例、产品..."
class="flex-1 text-[28rpx] text-gray-800 placeholder-gray-400"
@confirm="handleSearch"
/>
<view v-if="searchKeyword" class="ml-[16rpx]" @tap="clearSearch">
<IconFont name="Close" class="text-gray-400" size="16" />
</view>
</view>
<!-- Filter Tabs -->
<view class="flex overflow-x-auto no-scrollbar mb-[40rpx] space-x-[24rpx]">
<view v-for="(tab, index) in tabs" :key="index"
class="px-[32rpx] py-[16rpx] rounded-full text-[28rpx] whitespace-nowrap transition-colors"
:class="activeTab === index ? 'bg-[#2563EB] text-white' : 'bg-[#F3F4F6] text-[#6B7280]'"
@tap="activeTab = index">
{{ tab }}
</view>
</view>
<!-- Search Results -->
<view v-if="searchResults.length > 0">
<!-- Result Count -->
<view class="text-[#6B7280] text-[24rpx] mb-[24rpx]">
找到 {{ searchResults.length }} 个相关结果
</view>
<!-- Results List -->
<view class="flex flex-col gap-[24rpx]">
<!-- Product Card -->
<view
v-for="(item, index) in searchResults"
:key="index"
class="bg-white rounded-[24rpx] overflow-hidden shadow-sm"
@tap="goToDetail(item)"
>
<!-- Image + Content Layout -->
<view class="flex gap-[24rpx] p-[24rpx]">
<!-- Image -->
<image
class="w-[200rpx] h-[140rpx] rounded-[16rpx] bg-gray-100 flex-shrink-0"
:src="item.image"
mode="aspectFill"
/>
<!-- Content -->
<view class="flex-1 flex flex-col justify-between py-[4rpx]">
<!-- Title -->
<view class="text-[#1F2937] text-[28rpx] font-medium leading-[1.4] line-clamp-2">
{{ item.title }}
</view>
<!-- Meta Info -->
<view class="flex justify-between items-center">
<view class="flex gap-[12rpx]">
<!-- Type Tag -->
<view
class="px-[12rpx] py-[4rpx] rounded-[8rpx] text-[22rpx]"
:class="item.type === '产品' ? 'bg-blue-50 text-blue-600' : 'bg-green-50 text-green-600'"
>
{{ item.type }}
</view>
<!-- Hot Tag -->
<view v-if="item.tag" class="bg-red-50 text-red-600 text-[22rpx] px-[12rpx] py-[4rpx] rounded-[8rpx]">
{{ item.tag }}
</view>
</view>
<view class="text-[#6B7280] text-[24rpx]">
{{ item.views || 0 }}人查看
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- Empty State -->
<view v-else-if="hasSearched" class="flex flex-col items-center justify-center py-[120rpx]">
<image
class="w-[320rpx] h-[320rpx] mb-[40rpx]"
src="https://picsum.photos/seed/empty/320/320"
mode="aspectFit"
/>
<view class="text-[#6B7280] text-[28rpx]">暂无搜索结果</view>
<view class="text-[#9CA3AF] text-[24rpx] mt-[12rpx]">试试其他关键词吧</view>
</view>
<!-- Initial State -->
<view v-else class="flex flex-col items-center justify-center py-[120rpx]">
<IconFont name="Search" class="text-gray-300 mb-[24rpx]" size="64" />
<view class="text-[#6B7280] text-[28rpx]">搜索培训资料、案例、产品</view>
<view class="text-[#9CA3AF] text-[24rpx] mt-[12rpx]">输入关键词开始搜索</view>
</view>
<!-- Hot Searches -->
<view v-if="!hasSearched" class="mt-[60rpx]">
<view class="text-[#1F2937] text-[28rpx] font-bold mb-[24rpx]">热门搜索</view>
<view class="flex flex-wrap gap-[16rpx]">
<view
v-for="(keyword, index) in hotSearches"
:key="index"
class="px-[24rpx] py-[12rpx] bg-white rounded-full text-[26rpx] text-[#4B5563]"
@tap="searchKeyword = keyword; handleSearch()"
>
{{ keyword }}
</view>
</view>
</view>
</view>
<!-- Tab Bar -->
<TabBar current="home" />
</view>
</template>
<script setup>
import { ref, computed } from 'vue'
import Taro from '@tarojs/taro'
import { useGo } from '@/hooks/useGo'
import NavHeader from '@/components/NavHeader.vue'
import TabBar from '@/components/TabBar.vue'
import IconFont from '@/components/IconFont.vue'
// Navigation
const go = useGo()
// State
const searchKeyword = ref('')
const activeTab = ref(0)
const hasSearched = ref(false)
// Tabs
const tabs = ['全部', '产品', '资料']
// Hot searches
const hotSearches = ref([
'家庭财富传承',
'儿童教育金',
'医疗保险',
'高净值客户',
'保险合同',
])
// Mock data
const mockData = ref([
{
id: 1,
title: '家庭财富传承保障计划(分红)',
type: '产品',
tag: '热卖',
views: 256,
image: 'https://picsum.photos/seed/prod1/200/140',
category: 'product'
},
{
id: 2,
title: '2024年保险市场趋势分析报告',
type: '资料',
views: 189,
image: 'https://picsum.photos/seed/mat1/200/140',
category: 'material'
},
{
id: 3,
title: '儿童教育金储备方案(分红)',
type: '产品',
tag: '推荐',
views: 342,
image: 'https://picsum.photos/seed/prod2/200/140',
category: 'product'
},
{
id: 4,
title: '高净值客户需求分析与产品匹配',
type: '资料',
views: 142,
image: 'https://picsum.photos/seed/mat2/200/140',
category: 'material'
},
{
id: 5,
title: '百万医疗保险计划',
type: '产品',
views: 267,
image: 'https://picsum.photos/seed/prod3/200/140',
category: 'product'
},
{
id: 6,
title: '保险合同条款解读与风险提示',
type: '资料',
views: 198,
image: 'https://picsum.photos/seed/mat3/200/140',
category: 'material'
},
{
id: 7,
title: '意外伤害保障计划',
type: '产品',
tag: '热卖',
views: 223,
image: 'https://picsum.photos/seed/prod4/200/140',
category: 'product'
},
{
id: 8,
title: '保险销售实战技巧分享',
type: '资料',
views: 156,
image: 'https://picsum.photos/seed/mat4/200/140',
category: 'material'
},
])
// Search results
const searchResults = computed(() => {
if (!hasSearched.value) return []
let results = mockData.value
// Filter by tab
if (activeTab.value === 1) {
results = results.filter(item => item.category === 'product')
} else if (activeTab.value === 2) {
results = results.filter(item => item.category === 'material')
}
// Filter by keyword
if (searchKeyword.value.trim()) {
const keyword = searchKeyword.value.toLowerCase()
results = results.filter(item =>
item.title.toLowerCase().includes(keyword)
)
}
return results
})
// Handle search
const handleSearch = () => {
if (searchKeyword.value.trim()) {
hasSearched.value = true
}
}
// Clear search
const clearSearch = () => {
searchKeyword.value = ''
hasSearched.value = false
}
// Go to detail
const goToDetail = (item) => {
if (item.category === 'product') {
go('/pages/knowledge-base/index')
} else {
go('/pages/knowledge-base/index')
}
Taro.showToast({
title: `查看${item.type}详情`,
icon: 'none',
duration: 1500
})
}
</script>
<style>
.no-scrollbar::-webkit-scrollbar {
display: none;
}
.no-scrollbar {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>