hookehuyr

feat(SearchPopup): 替换模拟数据为真实API接口调用

- 移除模拟数据生成逻辑,改为调用真实API获取车辆数据
- 添加品牌、年份和学校筛选选项的动态加载
- 修改车辆展示字段以匹配API返回数据结构
- 优化搜索逻辑,支持多种筛选条件组合
- 添加分页加载更多功能
<!--
* @Date: 2025-01-20 00:00:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-06 18:08:56
* @LastEditTime: 2025-07-11 11:29:51
* @FilePath: /jgdl/src/components/SearchPopup.vue
* @Description: 搜索弹窗组件
-->
......@@ -63,24 +63,24 @@
<view v-for="scooter in searchResults" :key="scooter.id"
class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)">
<view class="relative p-2">
<image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill"
<image :src="scooter.front_photo" mode="aspectFill"
class="w-full h-36 object-cover rounded-lg" />
<view class="absolute top-4 right-3 w-7 h-7 rounded-full bg-white bg-opacity-80 flex items-center justify-center" @tap.stop="() => toggleFavorite(scooter)">
<Heart1 v-if="!scooter.is_favorite" size="22" :color="'#9ca3af'" />
<HeartFill v-else size="22" :color="'#ef4444'" />
</view>
<view v-if="scooter.isVerified"
<view v-if="scooter.verification_status === 5"
class="absolute bottom-4 right-4 text-white text-xs px-1.5 py-0.5 rounded flex items-center" style="background-color: #EB5305;">
<Check size="12" color="#ffffff" class="mr-0.5" />
<text class="text-white">认证</text>
</view>
</view>
<view class="p-2 pl-3">
<text class="font-medium text-sm block">{{ scooter.name }}</text>
<text class="font-medium text-sm block">{{ scooter.brand }} {{ scooter.model }}</text>
<text class="text-xs text-gray-500 block mt-1 mb-1">
{{ scooter.year }} · {{ scooter.school }}
<text v-if="scooter.batteryHealth">电池健康度{{ scooter.batteryHealth }}%</text>
<text v-if="scooter.mileage"> 行驶{{ scooter.mileage }}公里</text>
{{ scooter.manufacture_year }} · {{ scooter.school_name }}
<text v-if="scooter.range_km">续航{{ scooter.range_km }}km</text>
<text v-if="scooter.max_speed_kmh"> 最高时速{{ scooter.max_speed_kmh }}km/h</text>
</text>
<view class="mt-1">
<text class="text-orange-500 font-bold" style="font-size: 1.25rem;">
......@@ -128,11 +128,15 @@
</template>
<script setup>
import { ref, computed, watch } from 'vue'
import { ref, computed, watch, onMounted } from 'vue'
import Taro from '@tarojs/taro'
import { Search2, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { useFavorite } from '@/composables/useFavorite'
import "./SearchPopup.less"
// 接口导入
import { getVehicleBrandsAPI, getSchoolsAPI } from '@/api/other';
import { getVehicleListAPI } from '@/api/car';
import { DEFAULT_COVER_IMG } from '@/utils/config'
// Props
const props = defineProps({
......@@ -159,42 +163,21 @@ const hasSearched = ref(false)
// 滚动相关
const loading = ref(false)
const noMoreData = ref(false)
const currentPage = ref(1)
const currentPage = ref(0)
const pageSize = ref(10)
const totalCount = ref(0)
// Filter states
const selectedBrand = ref('全部品牌')
const selectedYear = ref('出厂年份')
const selectedSchool = ref('所在学校')
const selectedBrand = ref('')
const selectedYear = ref('')
const selectedSchool = ref('')
// Menu选项数据
const brandOptions = ref([
{ text: '全部品牌', value: '全部品牌' },
{ text: '雅迪', value: '雅迪' },
{ text: '台铃', value: '台铃' },
{ text: '小鸟', value: '小鸟' },
{ text: '新日', value: '新日' },
{ text: '爱玛', value: '爱玛' },
{ text: '小牛', value: '小牛' }
])
const yearOptions = ref([
{ text: '出厂年份', value: '出厂年份' },
{ text: '2024年', value: '2024年' },
{ text: '2023年', value: '2023年' },
{ text: '2022年', value: '2022年' },
{ text: '2021年', value: '2021年' },
{ text: '2020年', value: '2020年' }
])
const schoolOptions = ref([
{ text: '所在学校', value: '所在学校' },
{ text: '上海理工大学', value: '上海理工大学' },
{ text: '上海复旦大学', value: '上海复旦大学' },
{ text: '上海同济大学', value: '上海同济大学' },
{ text: '上海交通大学', value: '上海交通大学' }
])
const brandOptions = ref([])
const yearOptions = ref([])
const schoolOptions = ref([])
// 搜索结果数据
const searchResults = ref([])
......@@ -207,70 +190,78 @@ const scrollStyle = computed(() => {
})
/**
* 生成模拟数据
* @param {number} page 页码
* @param {number} size 每页数量
* @returns {Array} 模拟数据数组
* 加载车辆数据
* @param {boolean} isLoadMore - 是否为加载更多
*/
const generateMockData = (page, size) => {
const brands = ['雅迪', '台铃', '小鸟', '新日', '爱玛', '小牛']
const schools = ['上海理工大学', '上海复旦大学', '上海同济大学', '上海交通大学']
const years = ['2024年', '2023年', '2022年', '2021年', '2020年']
const images = [
'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1591637333184-19aa84b3e01f?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1558980664-3a031cf67ea8?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1595941069915-4ebc5197c14a?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
]
const data = []
const startId = (page - 1) * size + 1
for (let i = 0; i < size; i++) {
const brand = brands[Math.floor(Math.random() * brands.length)]
const school = schools[Math.floor(Math.random() * schools.length)]
const year = years[Math.floor(Math.random() * years.length)]
const image = images[Math.floor(Math.random() * images.length)]
data.push({
id: `search_${startId + i}`,
name: `${brand} ${['豪华版', '标准版', '运动版', '经典版'][Math.floor(Math.random() * 4)]}`,
year: year,
school: school,
price: Math.floor(Math.random() * 5000) + 2000,
imageUrl: image,
batteryHealth: Math.floor(Math.random() * 30) + 70,
mileage: Math.floor(Math.random() * 5000) + 500,
brand: brand,
isVerified: Math.random() > 0.6
})
}
const loadVehicleData = async (isLoadMore = false) => {
if (loading.value) return
loading.value = true
try {
// 构建请求参数
const params = {
page: currentPage.value,
limit: pageSize.value
}
// 添加筛选条件
if (selectedSchool.value) {
params.school_id = selectedSchool.value
}
if (selectedBrand.value) {
params.brand = selectedBrand.value
}
if (selectedYear.value) {
params.manufacture_year = selectedYear.value
}
if (searchValue.value.trim()) {
params.keyword = searchValue.value.trim()
}
const response = await getVehicleListAPI(params)
return data
if (response && response.code === 1 && response.data) {
const vehicleList = response.data.list || []
// 处理图片数据
const processedData = vehicleList.map(item => ({
...item,
front_photo: item.front_photo || DEFAULT_COVER_IMG
}))
if (isLoadMore) {
searchResults.value.push(...processedData)
} else {
searchResults.value = processedData
}
// 更新总数
totalCount.value = response.data.total || 0
// 检查是否还有更多数据
const totalLoaded = (currentPage.value + 1) * pageSize.value
noMoreData.value = totalLoaded >= totalCount.value
} else {
console.error('API返回错误:', response)
}
} catch (error) {
console.error('加载车辆数据失败:', error)
} finally {
loading.value = false
}
}
/**
* 执行搜索
*/
const performSearch = () => {
if (!searchValue.value.trim()) {
clearSearchResults()
return
}
loading.value = true
hasSearched.value = true
currentPage.value = 1
const performSearch = async () => {
// 重置分页
currentPage.value = 0
noMoreData.value = false
hasSearched.value = true
// 模拟API调用延迟
setTimeout(() => {
const mockData = generateMockData(1, pageSize.value)
searchResults.value = mockData
totalCount.value = 50 // 模拟总数
loading.value = false
}, 500)
await loadVehicleData()
}
/**
......@@ -279,16 +270,25 @@ const performSearch = () => {
const clearSearchResults = () => {
searchResults.value = []
hasSearched.value = false
currentPage.value = 1
currentPage.value = 0
noMoreData.value = false
totalCount.value = 0
}
/**
* 检查是否有搜索条件
*/
const hasSearchConditions = () => {
return searchValue.value.trim() || selectedBrand.value || selectedYear.value || selectedSchool.value
}
/**
* 搜索事件处理
*/
const onSearch = () => {
performSearch()
if (hasSearchConditions()) {
performSearch()
}
}
/**
......@@ -297,21 +297,24 @@ const onSearch = () => {
const onBlurSearch = () => {
if (searchValue.value.trim()) {
performSearch()
} else {
// 如果搜索框为空,清除搜索结果
} else if (!hasSearchConditions()) {
// 如果搜索框为空且没有其他筛选条件,清除搜索结果
clearSearchResults()
}
}
const onClearSearch = () => {
clearSearchResults()
if (!hasSearchConditions()) {
clearSearchResults()
}
}
/**
* 品牌筛选变化
*/
const onBrandChange = () => {
if (hasSearched.value) {
// 如果有搜索条件或者之前有搜索历史,则执行搜索
if (hasSearchConditions() || hasSearched.value) {
performSearch()
}
}
......@@ -320,7 +323,8 @@ const onBrandChange = () => {
* 年份筛选变化
*/
const onYearChange = () => {
if (hasSearched.value) {
// 如果有搜索条件或者之前有搜索历史,则执行搜索
if (hasSearchConditions() || hasSearched.value) {
performSearch()
}
}
......@@ -329,7 +333,8 @@ const onYearChange = () => {
* 学校筛选变化
*/
const onSchoolChange = () => {
if (hasSearched.value) {
// 如果有搜索条件或者之前有搜索历史,则执行搜索
if (hasSearchConditions() || hasSearched.value) {
performSearch()
}
}
......@@ -337,24 +342,11 @@ const onSchoolChange = () => {
/**
* 加载更多数据
*/
const loadMore = () => {
if (loading.value || noMoreData.value) return
const loadMore = async () => {
if (loading.value || noMoreData.value || !hasSearched.value) return
loading.value = true
currentPage.value++
// 模拟API调用延迟
setTimeout(() => {
const mockData = generateMockData(currentPage.value, pageSize.value)
if (mockData.length === 0 || searchResults.value.length >= totalCount.value) {
noMoreData.value = true
} else {
searchResults.value.push(...mockData)
}
loading.value = false
}, 500)
await loadVehicleData(true)
}
// 使用收藏功能composables
......@@ -390,14 +382,62 @@ const resetSearchState = () => {
hasSearched.value = false
loading.value = false
noMoreData.value = false
currentPage.value = 1
currentPage.value = 0
totalCount.value = 0
// 重置筛选条件
selectedBrand.value = '全部品牌'
selectedYear.value = '出厂年份'
selectedSchool.value = '所在学校'
selectedBrand.value = ''
selectedYear.value = ''
selectedSchool.value = ''
}
// 初始化筛选选项数据
const initFilterOptions = async () => {
try {
// 获取全部品牌数据
const vBrands = await getVehicleBrandsAPI()
if (vBrands.code) {
brandOptions.value = vBrands.data.map(brand => ({
text: brand,
value: brand
}))
brandOptions.value = [{
text: '全部品牌',
value: ''
}, ...brandOptions.value]
}
// 生成从当前日期开始往前10年的年份
yearOptions.value = Array.from({ length: 10 }, (_, i) => ({
text: (new Date().getFullYear() - i).toString() + '年',
value: (new Date().getFullYear() - i).toString()
}))
yearOptions.value = [{
text: '全部年份',
value: ''
}, ...yearOptions.value]
// 获取全部学校数据
const schoolData = await getSchoolsAPI()
if (schoolData.code) {
schoolOptions.value = schoolData.data.map(school => ({
text: school.name,
value: school.id
}))
}
schoolOptions.value = [{
text: '全部学校',
value: ''
}, ...schoolOptions.value]
} catch (error) {
console.error('初始化筛选选项失败:', error)
}
}
// 组件挂载时初始化筛选选项
onMounted(() => {
initFilterOptions()
})
// 监听visible.value的变化
watch(visible, (newVisible) => {
if (newVisible) {
......