feat: 添加搜索弹窗组件并修改首页搜索交互
实现搜索弹窗组件,包含搜索框、筛选条件和结果展示功能 修改首页搜索框交互,点击后显示搜索弹窗而非跳转页面
Showing
4 changed files
with
553 additions
and
7 deletions
| ... | @@ -39,6 +39,7 @@ declare module 'vue' { | ... | @@ -39,6 +39,7 @@ declare module 'vue' { |
| 39 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] | 39 | PosterBuilder: typeof import('./src/components/PosterBuilder/index.vue')['default'] |
| 40 | RouterLink: typeof import('vue-router')['RouterLink'] | 40 | RouterLink: typeof import('vue-router')['RouterLink'] |
| 41 | RouterView: typeof import('vue-router')['RouterView'] | 41 | RouterView: typeof import('vue-router')['RouterView'] |
| 42 | + SearchPopup: typeof import('./src/components/SearchPopup.vue')['default'] | ||
| 42 | TabBar: typeof import('./src/components/TabBar.vue')['default'] | 43 | TabBar: typeof import('./src/components/TabBar.vue')['default'] |
| 43 | } | 44 | } |
| 44 | } | 45 | } | ... | ... |
src/components/SearchPopup.less
0 → 100644
| 1 | +/* 搜索弹窗样式 */ | ||
| 2 | +.search-page { | ||
| 3 | + background-color: #f5f5f5; | ||
| 4 | + min-height: 100vh; | ||
| 5 | +} | ||
| 6 | + | ||
| 7 | +/* 搜索头部固定定位 */ | ||
| 8 | +.search-header { | ||
| 9 | + position: fixed; | ||
| 10 | + top: 0; | ||
| 11 | + left: 0; | ||
| 12 | + right: 0; | ||
| 13 | + z-index: 1000; | ||
| 14 | + background-color: #fff; | ||
| 15 | +} | ||
| 16 | + | ||
| 17 | +/* 搜索内容区域 */ | ||
| 18 | +.search-content { | ||
| 19 | + margin-top: 200rpx; /* 为固定头部预留空间 */ | ||
| 20 | +} | ||
| 21 | + | ||
| 22 | +/* 搜索结果列表样式 */ | ||
| 23 | +.search-results-list { | ||
| 24 | + width: 100%; | ||
| 25 | + box-sizing: border-box; | ||
| 26 | + | ||
| 27 | + /* 滚动条样式 */ | ||
| 28 | + &::-webkit-scrollbar { | ||
| 29 | + width: 6rpx; | ||
| 30 | + } | ||
| 31 | + | ||
| 32 | + &::-webkit-scrollbar-track { | ||
| 33 | + background: #f1f1f1; | ||
| 34 | + border-radius: 3rpx; | ||
| 35 | + } | ||
| 36 | + | ||
| 37 | + &::-webkit-scrollbar-thumb { | ||
| 38 | + background: #c1c1c1; | ||
| 39 | + border-radius: 3rpx; | ||
| 40 | + | ||
| 41 | + &:hover { | ||
| 42 | + background: #a8a8a8; | ||
| 43 | + } | ||
| 44 | + } | ||
| 45 | +} | ||
| 46 | + | ||
| 47 | +/* 加载状态样式 */ | ||
| 48 | +.load-more-container { | ||
| 49 | + padding: 40rpx 0; | ||
| 50 | + | ||
| 51 | + .loading-container { | ||
| 52 | + display: flex; | ||
| 53 | + align-items: center; | ||
| 54 | + justify-content: center; | ||
| 55 | + color: #666; | ||
| 56 | + | ||
| 57 | + .loading-spinner { | ||
| 58 | + width: 32rpx; | ||
| 59 | + height: 32rpx; | ||
| 60 | + border: 4rpx solid #f3f3f3; | ||
| 61 | + border-top: 4rpx solid #f97316; | ||
| 62 | + border-radius: 50%; | ||
| 63 | + animation: spin 1s linear infinite; | ||
| 64 | + margin-right: 16rpx; | ||
| 65 | + } | ||
| 66 | + | ||
| 67 | + .loading-text { | ||
| 68 | + font-size: 28rpx; | ||
| 69 | + color: #666; | ||
| 70 | + } | ||
| 71 | + } | ||
| 72 | + | ||
| 73 | + .no-more-data { | ||
| 74 | + padding: 20rpx 0; | ||
| 75 | + text-align: center; | ||
| 76 | + color: #999; | ||
| 77 | + font-size: 28rpx; | ||
| 78 | + } | ||
| 79 | +} | ||
| 80 | + | ||
| 81 | +/* 旋转动画 */ | ||
| 82 | +@keyframes spin { | ||
| 83 | + 0% { transform: rotate(0deg); } | ||
| 84 | + 100% { transform: rotate(360deg); } | ||
| 85 | +} | ||
| 86 | + | ||
| 87 | +/* 空状态样式 */ | ||
| 88 | +.empty-state { | ||
| 89 | + .empty-icon { | ||
| 90 | + display: flex; | ||
| 91 | + justify-content: center; | ||
| 92 | + align-items: center; | ||
| 93 | + opacity: 0.6; | ||
| 94 | + } | ||
| 95 | +} | ||
| 96 | + | ||
| 97 | +.no-results-state { | ||
| 98 | + .no-results-icon { | ||
| 99 | + display: flex; | ||
| 100 | + justify-content: center; | ||
| 101 | + align-items: center; | ||
| 102 | + opacity: 0.7; | ||
| 103 | + } | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +/* 搜索结果卡片样式优化 */ | ||
| 107 | +.search-results-list { | ||
| 108 | + .grid { | ||
| 109 | + .bg-white { | ||
| 110 | + transition: all 0.3s ease; | ||
| 111 | + border: 1rpx solid #e5e7eb; | ||
| 112 | + | ||
| 113 | + &:hover { | ||
| 114 | + box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.12); | ||
| 115 | + transform: translateY(-2rpx); | ||
| 116 | + } | ||
| 117 | + } | ||
| 118 | + } | ||
| 119 | +} | ||
| ... | \ No newline at end of file | ... | \ No newline at end of file |
src/components/SearchPopup.vue
0 → 100644
| 1 | +<!-- | ||
| 2 | + * @Date: 2025-01-20 00:00:00 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-07-04 16:05:59 | ||
| 5 | + * @FilePath: /jgdl/src/components/SearchPopup.vue | ||
| 6 | + * @Description: 搜索弹窗组件 | ||
| 7 | +--> | ||
| 8 | +<template> | ||
| 9 | + <nut-popup | ||
| 10 | + v-model:visible="visible" | ||
| 11 | + position="right" | ||
| 12 | + :style="{ width: '100%', height: '100%' }" | ||
| 13 | + :catch-move="true" | ||
| 14 | + @close="handleClose" | ||
| 15 | + > | ||
| 16 | + <view class="search-page"> | ||
| 17 | + <view class="flex flex-col bg-white min-h-screen"> | ||
| 18 | + <!-- Header --> | ||
| 19 | + <view class="search-header"> | ||
| 20 | + <view class="bg-orange-400 p-4 pt-4 pb-4"> | ||
| 21 | + <nut-row type="flex" justify="center" align="center"> | ||
| 22 | + <nut-col span="3"> | ||
| 23 | + <view class="text-xl font-bold text-white" @tap="handleClose">关闭</view> | ||
| 24 | + </nut-col> | ||
| 25 | + <nut-col span="21"> | ||
| 26 | + <!-- Search Bar --> | ||
| 27 | + <nut-searchbar v-model="searchValue" placeholder="搜索商品名称、品牌、型号" @search="onSearch" @blur="onBlurSearch" @clear="onClearSearch" shape="round" background="transparent" input-background="#ffffff"> | ||
| 28 | + <template #leftin> | ||
| 29 | + <Search2 /> | ||
| 30 | + </template> | ||
| 31 | + </nut-searchbar> | ||
| 32 | + </nut-col> | ||
| 33 | + </nut-row> | ||
| 34 | + </view> | ||
| 35 | + | ||
| 36 | + <!-- Filter options --> | ||
| 37 | + <nut-menu> | ||
| 38 | + <nut-menu-item v-model="selectedBrand" :options="brandOptions" @change="onBrandChange" /> | ||
| 39 | + <nut-menu-item v-model="selectedYear" :options="yearOptions" @change="onYearChange" /> | ||
| 40 | + <nut-menu-item v-model="selectedSchool" :options="schoolOptions" @change="onSchoolChange" /> | ||
| 41 | + </nut-menu> | ||
| 42 | + </view> | ||
| 43 | + | ||
| 44 | + <!-- Search Results --> | ||
| 45 | + <view class="flex-1 search-content"> | ||
| 46 | + <!-- 滚动列表 --> | ||
| 47 | + <scroll-view | ||
| 48 | + class="search-results-list" | ||
| 49 | + :style="scrollStyle" | ||
| 50 | + :scroll-y="true" | ||
| 51 | + @scrolltolower="loadMore" | ||
| 52 | + :lower-threshold="50" | ||
| 53 | + :enable-flex="false" | ||
| 54 | + > | ||
| 55 | + <view class="p-4 pt-12"> | ||
| 56 | + <!-- 搜索结果统计 --> | ||
| 57 | + <view v-if="searchResults.length > 0" class="mb-4"> | ||
| 58 | + <text class="text-sm text-gray-600">找到 {{ totalCount }} 个相关结果</text> | ||
| 59 | + </view> | ||
| 60 | + | ||
| 61 | + <!-- 搜索结果网格布局 --> | ||
| 62 | + <view class="grid grid-cols-2 gap-3"> | ||
| 63 | + <view v-for="scooter in searchResults" :key="scooter.id" | ||
| 64 | + class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)"> | ||
| 65 | + <view class="relative p-2"> | ||
| 66 | + <image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill" | ||
| 67 | + class="w-full h-36 object-cover rounded-lg" /> | ||
| 68 | + <view class="absolute top-4 right-3 w-7 h-7 rounded-full bg-white bg-opacity-90" @tap.stop="() => toggleFavorite(scooter.id)" style="padding-top: 12rpx; padding-left: 10rpx;"> | ||
| 69 | + <Heart1 v-if="!favoriteIds.includes(scooter.id)" size="22" :color="'#9ca3af'" /> | ||
| 70 | + <HeartFill v-else size="22" :color="'#ef4444'" /> | ||
| 71 | + </view> | ||
| 72 | + <view v-if="scooter.isVerified" | ||
| 73 | + class="absolute bottom-4 right-4 text-white text-xs px-1.5 py-0.5 rounded flex items-center" style="background-color: #EB5305;"> | ||
| 74 | + <Check size="12" color="#ffffff" class="mr-0.5" /> | ||
| 75 | + <text class="text-white">认证</text> | ||
| 76 | + </view> | ||
| 77 | + </view> | ||
| 78 | + <view class="p-2 pl-3"> | ||
| 79 | + <text class="font-medium text-sm block">{{ scooter.name }}</text> | ||
| 80 | + <text class="text-xs text-gray-500 block mt-1 mb-1"> | ||
| 81 | + {{ scooter.year }} · {{ scooter.school }} | ||
| 82 | + <text v-if="scooter.batteryHealth">电池健康度{{ scooter.batteryHealth }}%</text> | ||
| 83 | + <text v-if="scooter.mileage"> 行驶{{ scooter.mileage }}公里</text> | ||
| 84 | + </text> | ||
| 85 | + <view class="mt-1"> | ||
| 86 | + <text class="text-orange-500 font-bold" style="font-size: 1.25rem;"> | ||
| 87 | + ¥{{ scooter.price.toLocaleString() }} | ||
| 88 | + </text> | ||
| 89 | + </view> | ||
| 90 | + </view> | ||
| 91 | + </view> | ||
| 92 | + </view> | ||
| 93 | + | ||
| 94 | + <!-- 初始空状态 --> | ||
| 95 | + <view v-if="searchResults.length === 0 && !loading && !hasSearched" class="empty-state text-center py-20"> | ||
| 96 | + <view class="empty-icon mb-4"> | ||
| 97 | + <Search2 size="80" color="#d1d5db" /> | ||
| 98 | + </view> | ||
| 99 | + <text class="text-lg font-medium text-gray-600 block mb-2">搜索电动车</text> | ||
| 100 | + <text class="text-sm text-gray-400 block">输入品牌型号,找到心仪的电动车</text> | ||
| 101 | + </view> | ||
| 102 | + | ||
| 103 | + <!-- 搜索无结果状态 --> | ||
| 104 | + <view v-if="searchResults.length === 0 && !loading && hasSearched" class="no-results-state text-center py-20"> | ||
| 105 | + <view class="no-results-icon mb-4"> | ||
| 106 | + <Search2 size="60" color="#9ca3af" /> | ||
| 107 | + </view> | ||
| 108 | + <text class="text-base font-medium text-gray-600 block mb-2">暂无搜索结果</text> | ||
| 109 | + <text class="text-sm text-gray-400 block">试试其他关键词或调整筛选条件</text> | ||
| 110 | + </view> | ||
| 111 | + | ||
| 112 | + <!-- 加载更多 --> | ||
| 113 | + <view v-if="searchResults.length > 0" class="load-more-container mt-6"> | ||
| 114 | + <view v-if="loading" class="loading-container"> | ||
| 115 | + <view class="loading-spinner"></view> | ||
| 116 | + <text class="loading-text">加载中...</text> | ||
| 117 | + </view> | ||
| 118 | + <view v-else-if="noMoreData" class="no-more-data text-center"> | ||
| 119 | + <text class="text-gray-500">没有更多数据了</text> | ||
| 120 | + </view> | ||
| 121 | + </view> | ||
| 122 | + </view> | ||
| 123 | + </scroll-view> | ||
| 124 | + </view> | ||
| 125 | + </view> | ||
| 126 | + </view> | ||
| 127 | + </nut-popup> | ||
| 128 | +</template> | ||
| 129 | + | ||
| 130 | +<script setup> | ||
| 131 | +import { ref, computed, onMounted } from 'vue' | ||
| 132 | +import Taro from '@tarojs/taro' | ||
| 133 | +import { Search2, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro' | ||
| 134 | +import "./SearchPopup.less" | ||
| 135 | + | ||
| 136 | +// Props | ||
| 137 | +const props = defineProps({ | ||
| 138 | + visible: { | ||
| 139 | + type: Boolean, | ||
| 140 | + default: false | ||
| 141 | + } | ||
| 142 | +}) | ||
| 143 | + | ||
| 144 | +// Emits | ||
| 145 | +const emit = defineEmits(['update:visible']) | ||
| 146 | + | ||
| 147 | +// 内部可见状态 | ||
| 148 | +const visible = computed({ | ||
| 149 | + get: () => props.visible, | ||
| 150 | + set: (value) => emit('update:visible', value) | ||
| 151 | +}) | ||
| 152 | + | ||
| 153 | +// 响应式数据 | ||
| 154 | +const searchValue = ref('') | ||
| 155 | +const favoriteIds = ref(['5', '7', '1']) | ||
| 156 | +const hasSearched = ref(false) | ||
| 157 | + | ||
| 158 | +// 滚动相关 | ||
| 159 | +const loading = ref(false) | ||
| 160 | +const noMoreData = ref(false) | ||
| 161 | +const currentPage = ref(1) | ||
| 162 | +const pageSize = ref(10) | ||
| 163 | +const totalCount = ref(0) | ||
| 164 | + | ||
| 165 | +// Filter states | ||
| 166 | +const selectedBrand = ref('全部品牌') | ||
| 167 | +const selectedYear = ref('出厂年份') | ||
| 168 | +const selectedSchool = ref('所在学校') | ||
| 169 | + | ||
| 170 | +// Menu选项数据 | ||
| 171 | +const brandOptions = ref([ | ||
| 172 | + { text: '全部品牌', value: '全部品牌' }, | ||
| 173 | + { text: '雅迪', value: '雅迪' }, | ||
| 174 | + { text: '台铃', value: '台铃' }, | ||
| 175 | + { text: '小鸟', value: '小鸟' }, | ||
| 176 | + { text: '新日', value: '新日' }, | ||
| 177 | + { text: '爱玛', value: '爱玛' }, | ||
| 178 | + { text: '小牛', value: '小牛' } | ||
| 179 | +]) | ||
| 180 | + | ||
| 181 | +const yearOptions = ref([ | ||
| 182 | + { text: '出厂年份', value: '出厂年份' }, | ||
| 183 | + { text: '2024年', value: '2024年' }, | ||
| 184 | + { text: '2023年', value: '2023年' }, | ||
| 185 | + { text: '2022年', value: '2022年' }, | ||
| 186 | + { text: '2021年', value: '2021年' }, | ||
| 187 | + { text: '2020年', value: '2020年' } | ||
| 188 | +]) | ||
| 189 | + | ||
| 190 | +const schoolOptions = ref([ | ||
| 191 | + { text: '所在学校', value: '所在学校' }, | ||
| 192 | + { text: '上海理工大学', value: '上海理工大学' }, | ||
| 193 | + { text: '上海复旦大学', value: '上海复旦大学' }, | ||
| 194 | + { text: '上海同济大学', value: '上海同济大学' }, | ||
| 195 | + { text: '上海交通大学', value: '上海交通大学' } | ||
| 196 | +]) | ||
| 197 | + | ||
| 198 | +// 搜索结果数据 | ||
| 199 | +const searchResults = ref([]) | ||
| 200 | + | ||
| 201 | +// 滚动样式 | ||
| 202 | +const scrollStyle = computed(() => { | ||
| 203 | + return { | ||
| 204 | + height: 'calc(100vh - 200rpx)' | ||
| 205 | + } | ||
| 206 | +}) | ||
| 207 | + | ||
| 208 | +/** | ||
| 209 | + * 生成模拟数据 | ||
| 210 | + * @param {number} page 页码 | ||
| 211 | + * @param {number} size 每页数量 | ||
| 212 | + * @returns {Array} 模拟数据数组 | ||
| 213 | + */ | ||
| 214 | +const generateMockData = (page, size) => { | ||
| 215 | + const brands = ['雅迪', '台铃', '小鸟', '新日', '爱玛', '小牛'] | ||
| 216 | + const schools = ['上海理工大学', '上海复旦大学', '上海同济大学', '上海交通大学'] | ||
| 217 | + const years = ['2024年', '2023年', '2022年', '2021年', '2020年'] | ||
| 218 | + const images = [ | ||
| 219 | + 'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 220 | + 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 221 | + 'https://images.unsplash.com/photo-1591637333184-19aa84b3e01f?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 222 | + 'https://images.unsplash.com/photo-1558980664-3a031cf67ea8?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 223 | + 'https://images.unsplash.com/photo-1595941069915-4ebc5197c14a?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60' | ||
| 224 | + ] | ||
| 225 | + | ||
| 226 | + const data = [] | ||
| 227 | + const startId = (page - 1) * size + 1 | ||
| 228 | + | ||
| 229 | + for (let i = 0; i < size; i++) { | ||
| 230 | + const brand = brands[Math.floor(Math.random() * brands.length)] | ||
| 231 | + const school = schools[Math.floor(Math.random() * schools.length)] | ||
| 232 | + const year = years[Math.floor(Math.random() * years.length)] | ||
| 233 | + const image = images[Math.floor(Math.random() * images.length)] | ||
| 234 | + | ||
| 235 | + data.push({ | ||
| 236 | + id: `search_${startId + i}`, | ||
| 237 | + name: `${brand} ${['豪华版', '标准版', '运动版', '经典版'][Math.floor(Math.random() * 4)]}`, | ||
| 238 | + year: year, | ||
| 239 | + school: school, | ||
| 240 | + price: Math.floor(Math.random() * 5000) + 2000, | ||
| 241 | + imageUrl: image, | ||
| 242 | + batteryHealth: Math.floor(Math.random() * 30) + 70, | ||
| 243 | + mileage: Math.floor(Math.random() * 5000) + 500, | ||
| 244 | + brand: brand, | ||
| 245 | + isVerified: Math.random() > 0.6 | ||
| 246 | + }) | ||
| 247 | + } | ||
| 248 | + | ||
| 249 | + return data | ||
| 250 | +} | ||
| 251 | + | ||
| 252 | +/** | ||
| 253 | + * 执行搜索 | ||
| 254 | + */ | ||
| 255 | +const performSearch = () => { | ||
| 256 | + if (!searchValue.value.trim()) { | ||
| 257 | + clearSearchResults() | ||
| 258 | + return | ||
| 259 | + } | ||
| 260 | + | ||
| 261 | + loading.value = true | ||
| 262 | + hasSearched.value = true | ||
| 263 | + currentPage.value = 1 | ||
| 264 | + noMoreData.value = false | ||
| 265 | + | ||
| 266 | + // 模拟API调用延迟 | ||
| 267 | + setTimeout(() => { | ||
| 268 | + const mockData = generateMockData(1, pageSize.value) | ||
| 269 | + searchResults.value = mockData | ||
| 270 | + totalCount.value = 50 // 模拟总数 | ||
| 271 | + loading.value = false | ||
| 272 | + }, 500) | ||
| 273 | +} | ||
| 274 | + | ||
| 275 | +/** | ||
| 276 | + * 清除搜索结果 | ||
| 277 | + */ | ||
| 278 | +const clearSearchResults = () => { | ||
| 279 | + searchResults.value = [] | ||
| 280 | + hasSearched.value = false | ||
| 281 | + currentPage.value = 1 | ||
| 282 | + noMoreData.value = false | ||
| 283 | + totalCount.value = 0 | ||
| 284 | +} | ||
| 285 | + | ||
| 286 | +/** | ||
| 287 | + * 搜索事件处理 | ||
| 288 | + */ | ||
| 289 | +const onSearch = () => { | ||
| 290 | + performSearch() | ||
| 291 | +} | ||
| 292 | + | ||
| 293 | +/** | ||
| 294 | + * 搜索框失焦事件 | ||
| 295 | + */ | ||
| 296 | +const onBlurSearch = () => { | ||
| 297 | + if (searchValue.value.trim()) { | ||
| 298 | + performSearch() | ||
| 299 | + } else { | ||
| 300 | + // 如果搜索框为空,清除搜索结果 | ||
| 301 | + clearSearchResults() | ||
| 302 | + } | ||
| 303 | +} | ||
| 304 | + | ||
| 305 | +const onClearSearch = () => { | ||
| 306 | + clearSearchResults() | ||
| 307 | +} | ||
| 308 | + | ||
| 309 | +/** | ||
| 310 | + * 品牌筛选变化 | ||
| 311 | + */ | ||
| 312 | +const onBrandChange = () => { | ||
| 313 | + if (hasSearched.value) { | ||
| 314 | + performSearch() | ||
| 315 | + } | ||
| 316 | +} | ||
| 317 | + | ||
| 318 | +/** | ||
| 319 | + * 年份筛选变化 | ||
| 320 | + */ | ||
| 321 | +const onYearChange = () => { | ||
| 322 | + if (hasSearched.value) { | ||
| 323 | + performSearch() | ||
| 324 | + } | ||
| 325 | +} | ||
| 326 | + | ||
| 327 | +/** | ||
| 328 | + * 学校筛选变化 | ||
| 329 | + */ | ||
| 330 | +const onSchoolChange = () => { | ||
| 331 | + if (hasSearched.value) { | ||
| 332 | + performSearch() | ||
| 333 | + } | ||
| 334 | +} | ||
| 335 | + | ||
| 336 | +/** | ||
| 337 | + * 加载更多数据 | ||
| 338 | + */ | ||
| 339 | +const loadMore = () => { | ||
| 340 | + if (loading.value || noMoreData.value) return | ||
| 341 | + | ||
| 342 | + loading.value = true | ||
| 343 | + currentPage.value++ | ||
| 344 | + | ||
| 345 | + // 模拟API调用延迟 | ||
| 346 | + setTimeout(() => { | ||
| 347 | + const mockData = generateMockData(currentPage.value, pageSize.value) | ||
| 348 | + | ||
| 349 | + if (mockData.length === 0 || searchResults.value.length >= totalCount.value) { | ||
| 350 | + noMoreData.value = true | ||
| 351 | + } else { | ||
| 352 | + searchResults.value.push(...mockData) | ||
| 353 | + } | ||
| 354 | + | ||
| 355 | + loading.value = false | ||
| 356 | + }, 500) | ||
| 357 | +} | ||
| 358 | + | ||
| 359 | +/** | ||
| 360 | + * 切换收藏状态 | ||
| 361 | + * @param {string} id 商品ID | ||
| 362 | + */ | ||
| 363 | +const toggleFavorite = (id) => { | ||
| 364 | + const index = favoriteIds.value.indexOf(id) | ||
| 365 | + if (index > -1) { | ||
| 366 | + favoriteIds.value.splice(index, 1) | ||
| 367 | + } else { | ||
| 368 | + favoriteIds.value.push(id) | ||
| 369 | + } | ||
| 370 | +} | ||
| 371 | + | ||
| 372 | +/** | ||
| 373 | + * 商品点击事件 | ||
| 374 | + * @param {Object} scooter 商品信息 | ||
| 375 | + */ | ||
| 376 | +const onProductClick = (scooter) => { | ||
| 377 | + // 关闭弹窗 | ||
| 378 | + handleClose() | ||
| 379 | + // 跳转到详情页 | ||
| 380 | + Taro.navigateTo({ | ||
| 381 | + url: `/pages/productDetail/index?id=${scooter.id}` | ||
| 382 | + }) | ||
| 383 | +} | ||
| 384 | + | ||
| 385 | +/** | ||
| 386 | + * 关闭弹窗 | ||
| 387 | + */ | ||
| 388 | +const handleClose = () => { | ||
| 389 | + // 重置搜索状态 | ||
| 390 | + resetSearchState() | ||
| 391 | + emit('update:visible', false) | ||
| 392 | +} | ||
| 393 | + | ||
| 394 | +/** | ||
| 395 | + * 重置搜索状态 | ||
| 396 | + */ | ||
| 397 | +const resetSearchState = () => { | ||
| 398 | + searchValue.value = '' | ||
| 399 | + searchResults.value = [] | ||
| 400 | + hasSearched.value = false | ||
| 401 | + loading.value = false | ||
| 402 | + noMoreData.value = false | ||
| 403 | + currentPage.value = 1 | ||
| 404 | + totalCount.value = 0 | ||
| 405 | + // 重置筛选条件 | ||
| 406 | + selectedBrand.value = '全部品牌' | ||
| 407 | + selectedYear.value = '出厂年份' | ||
| 408 | + selectedSchool.value = '所在学校' | ||
| 409 | +} | ||
| 410 | + | ||
| 411 | +onMounted(() => { | ||
| 412 | + // 动态修改标题 | ||
| 413 | + wx.setNavigationBarTitle({ | ||
| 414 | + title: '搜索车源' | ||
| 415 | + }); | ||
| 416 | +}) | ||
| 417 | +</script> | ||
| 418 | + | ||
| 419 | +<script> | ||
| 420 | +export default { | ||
| 421 | + name: "SearchPopup", | ||
| 422 | +}; | ||
| 423 | +</script> |
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2025-06-28 10:33:00 | 2 | * @Date: 2025-06-28 10:33:00 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-07-04 15:20:58 | 4 | + * @LastEditTime: 2025-07-04 16:04:15 |
| 5 | * @FilePath: /jgdl/src/pages/index/index.vue | 5 | * @FilePath: /jgdl/src/pages/index/index.vue |
| 6 | * @Description: 捡个电驴首页 | 6 | * @Description: 捡个电驴首页 |
| 7 | --> | 7 | --> |
| ... | @@ -16,7 +16,7 @@ | ... | @@ -16,7 +16,7 @@ |
| 16 | </nut-col> | 16 | </nut-col> |
| 17 | <nut-col span="18"> | 17 | <nut-col span="18"> |
| 18 | <!-- Search Bar --> | 18 | <!-- Search Bar --> |
| 19 | - <nut-searchbar v-model="searchValue" placeholder="搜索更多商品" @focus="onFocusSearch" shape="round" background="transparent" input-background="#ffffff"> | 19 | + <nut-searchbar v-model="searchValue" placeholder="搜索更多商品" :disabled="true" @click-input="onSearchHandle" shape="round" background="transparent" input-background="#ffffff"> |
| 20 | <template #leftin> | 20 | <template #leftin> |
| 21 | <Search2 /> | 21 | <Search2 /> |
| 22 | </template> | 22 | </template> |
| ... | @@ -153,6 +153,9 @@ | ... | @@ -153,6 +153,9 @@ |
| 153 | 153 | ||
| 154 | <!-- 自定义TabBar --> | 154 | <!-- 自定义TabBar --> |
| 155 | <TabBar /> | 155 | <TabBar /> |
| 156 | + | ||
| 157 | + <!-- 搜索弹窗 --> | ||
| 158 | + <SearchPopup v-model:visible="showSearchPopup" /> | ||
| 156 | </view> | 159 | </view> |
| 157 | </template> | 160 | </template> |
| 158 | 161 | ||
| ... | @@ -163,17 +166,17 @@ import { ref, onMounted } from 'vue' | ... | @@ -163,17 +166,17 @@ import { ref, onMounted } from 'vue' |
| 163 | import { useDidShow, useReady } from '@tarojs/taro' | 166 | import { useDidShow, useReady } from '@tarojs/taro' |
| 164 | import { Clock, Star, RectRight, Check, Search2, Shop, Heart1, HeartFill } from '@nutui/icons-vue-taro' | 167 | import { Clock, Star, RectRight, Check, Search2, Shop, Heart1, HeartFill } from '@nutui/icons-vue-taro' |
| 165 | import TabBar from '@/components/TabBar.vue' | 168 | import TabBar from '@/components/TabBar.vue' |
| 169 | +import SearchPopup from '@/components/SearchPopup.vue' | ||
| 166 | import "./index.less"; | 170 | import "./index.less"; |
| 167 | 171 | ||
| 168 | // 响应式数据 | 172 | // 响应式数据 |
| 169 | const searchValue = ref('') | 173 | const searchValue = ref('') |
| 170 | const favoriteIds = ref([]) | 174 | const favoriteIds = ref([]) |
| 175 | +const showSearchPopup = ref(false) | ||
| 171 | 176 | ||
| 172 | -const onFocusSearch = () => { | 177 | +const onSearchHandle = () => { |
| 173 | - // 跳转到搜索页面 | 178 | + // 显示搜索弹窗 |
| 174 | - Taro.navigateTo({ | 179 | + showSearchPopup.value = true |
| 175 | - url: '/pages/search/index' | ||
| 176 | - }) | ||
| 177 | } | 180 | } |
| 178 | 181 | ||
| 179 | // Banner图片 | 182 | // Banner图片 | ... | ... |
-
Please register or login to post a comment