feat(车辆列表): 替换模拟数据为真实API接口获取
- 移除本地模拟数据,改为调用getVehicleListAPI获取真实车辆数据 - 更新车辆卡片显示字段以匹配API返回数据结构 - 实现分页加载和筛选功能 - 添加数据加载状态管理和错误处理
Showing
1 changed file
with
94 additions
and
130 deletions
| 1 | <!-- | 1 | <!-- |
| 2 | * @Date: 2022-09-19 14:11:06 | 2 | * @Date: 2022-09-19 14:11:06 |
| 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com | 3 | * @LastEditors: hookehuyr hookehuyr@gmail.com |
| 4 | - * @LastEditTime: 2025-07-10 21:19:50 | 4 | + * @LastEditTime: 2025-07-10 22:39:18 |
| 5 | * @FilePath: /jgdl/src/pages/newCarList/index.vue | 5 | * @FilePath: /jgdl/src/pages/newCarList/index.vue |
| 6 | * @Description: 最新上架页面 | 6 | * @Description: 最新上架页面 |
| 7 | --> | 7 | --> |
| ... | @@ -53,7 +53,7 @@ | ... | @@ -53,7 +53,7 @@ |
| 53 | > | 53 | > |
| 54 | <view class="flex"> | 54 | <view class="flex"> |
| 55 | <view class="w-32 h-24 relative p-2"> | 55 | <view class="w-32 h-24 relative p-2"> |
| 56 | - <image :src="car.imageUrl" :alt="car.name" mode="aspectFill" | 56 | + <image :src="car.front_photo" mode="aspectFill" |
| 57 | class="w-full h-full object-cover rounded-lg" /> | 57 | class="w-full h-full object-cover rounded-lg" /> |
| 58 | <view v-if="car.isNew" | 58 | <view v-if="car.isNew" |
| 59 | class="absolute bottom-3 right-3 bg-red-500 text-white text-xs px-1 rounded flex items-center"> | 59 | class="absolute bottom-3 right-3 bg-red-500 text-white text-xs px-1 rounded flex items-center"> |
| ... | @@ -65,22 +65,22 @@ | ... | @@ -65,22 +65,22 @@ |
| 65 | <Heart1 v-if="!car.is_favorite" size="22" color="#9ca3af" /> | 65 | <Heart1 v-if="!car.is_favorite" size="22" color="#9ca3af" /> |
| 66 | <HeartFill v-else size="22" color="#ef4444" /> | 66 | <HeartFill v-else size="22" color="#ef4444" /> |
| 67 | </view> | 67 | </view> |
| 68 | - <text class="font-medium text-sm block">{{ car.name }}</text> | 68 | + <text class="font-medium text-sm block">{{ car.brand }} {{ car.model }}</text> |
| 69 | <text class="text-xs text-gray-600 mt-1 block"> | 69 | <text class="text-xs text-gray-600 mt-1 block"> |
| 70 | - {{ car.year }} · | 70 | + {{ car.manufacture_year }} · |
| 71 | - <text v-if="car.batteryHealth">电池健康度{{ car.batteryHealth }}%</text> | 71 | + <text v-if="car.range_km">续航{{ car.range_km }}km</text> |
| 72 | - <text v-if="car.mileage"> 行驶{{ car.mileage }}公里</text> | 72 | + <text v-if="car.max_speed_kmh"> 最高时速{{ car.max_speed_kmh }}km/h</text> |
| 73 | </text> | 73 | </text> |
| 74 | <view class="mt-2"> | 74 | <view class="mt-2"> |
| 75 | <text class="text-orange-500 font-bold" style="font-size: 1.2rem;"> | 75 | <text class="text-orange-500 font-bold" style="font-size: 1.2rem;"> |
| 76 | ¥{{ car.price.toLocaleString() }} | 76 | ¥{{ car.price.toLocaleString() }} |
| 77 | </text> | 77 | </text> |
| 78 | - <text class="text-xs text-gray-500 mt-1 block">{{ car.school }}</text> | 78 | + <text class="text-xs text-gray-500 mt-1 block">{{ car.school_name }}</text> |
| 79 | </view> | 79 | </view> |
| 80 | <!-- 上架时间 --> | 80 | <!-- 上架时间 --> |
| 81 | - <view class="mt-1"> | 81 | + <!-- <view class="mt-1"> |
| 82 | <text class="text-xs text-green-600">{{ car.listingTime }}</text> | 82 | <text class="text-xs text-green-600">{{ car.listingTime }}</text> |
| 83 | - </view> | 83 | + </view> --> |
| 84 | </view> | 84 | </view> |
| 85 | </view> | 85 | </view> |
| 86 | </view> | 86 | </view> |
| ... | @@ -118,15 +118,21 @@ import { Search2, Heart1, HeartFill } from '@nutui/icons-vue-taro' | ... | @@ -118,15 +118,21 @@ import { Search2, Heart1, HeartFill } from '@nutui/icons-vue-taro' |
| 118 | // TabBar 组件已移除 | 118 | // TabBar 组件已移除 |
| 119 | import { useFavorite } from '@/composables/useFavorite' | 119 | import { useFavorite } from '@/composables/useFavorite' |
| 120 | import './index.less' | 120 | import './index.less' |
| 121 | -// 接口导入已移除,现在使用模拟数据; | 121 | +// 接口导入 |
| 122 | import { getVehicleBrandsAPI, getSchoolsAPI } from '@/api/other'; | 122 | import { getVehicleBrandsAPI, getSchoolsAPI } from '@/api/other'; |
| 123 | +import { getVehicleListAPI } from '@/api/car'; | ||
| 123 | 124 | ||
| 124 | // 响应式数据 | 125 | // 响应式数据 |
| 125 | const searchValue = ref('') | 126 | const searchValue = ref('') |
| 126 | -const onBlurSearch = () => { | 127 | +/** |
| 127 | - console.warn(searchValue.value) | 128 | + * 搜索框失焦事件 |
| 129 | + */ | ||
| 130 | +const onBlurSearch = async () => { | ||
| 131 | + // 重置分页并重新加载数据 | ||
| 132 | + currentPage.value = 0 | ||
| 133 | + hasMore.value = true | ||
| 134 | + await loadVehicleData() | ||
| 128 | } | 135 | } |
| 129 | -// 收藏功能现在使用基于对象属性的模式 | ||
| 130 | 136 | ||
| 131 | // Filter states - 使用NutUI Menu组件 | 137 | // Filter states - 使用NutUI Menu组件 |
| 132 | const selectedBrand = ref('') | 138 | const selectedBrand = ref('') |
| ... | @@ -141,66 +147,13 @@ const yearOptions = ref([]) | ... | @@ -141,66 +147,13 @@ const yearOptions = ref([]) |
| 141 | const schoolOptions = ref([]) | 147 | const schoolOptions = ref([]) |
| 142 | 148 | ||
| 143 | // 最新上架车辆数据 | 149 | // 最新上架车辆数据 |
| 144 | -const newCars = ref([ | 150 | +const newCars = ref([]) |
| 145 | - { | ||
| 146 | - id: 1, | ||
| 147 | - name: '小牛NGT 电动车', | ||
| 148 | - year: '2024年', | ||
| 149 | - batteryHealth: 100, | ||
| 150 | - mileage: 0, | ||
| 151 | - price: 5200, | ||
| 152 | - school: '上海理工大学', | ||
| 153 | - imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop', | ||
| 154 | - listingTime: '刚刚上架', | ||
| 155 | - isNew: true, | ||
| 156 | - brand: '小牛' | ||
| 157 | - }, | ||
| 158 | - { | ||
| 159 | - id: 2, | ||
| 160 | - name: '雅迪 DE3 电动车', | ||
| 161 | - year: '2024年', | ||
| 162 | - batteryHealth: 98, | ||
| 163 | - mileage: 200, | ||
| 164 | - price: 4800, | ||
| 165 | - school: '上海大学', | ||
| 166 | - imageUrl: 'https://images.unsplash.com/photo-1571068316344-75bc76f77890?w=400&h=300&fit=crop', | ||
| 167 | - listingTime: '5分钟前上架', | ||
| 168 | - isNew: true, | ||
| 169 | - brand: '雅迪' | ||
| 170 | - }, | ||
| 171 | - { | ||
| 172 | - id: 3, | ||
| 173 | - name: '爱玛 A600 电动车', | ||
| 174 | - year: '2024年', | ||
| 175 | - batteryHealth: 95, | ||
| 176 | - mileage: 500, | ||
| 177 | - price: 3800, | ||
| 178 | - school: '华东理工大学', | ||
| 179 | - imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop', | ||
| 180 | - listingTime: '10分钟前上架', | ||
| 181 | - isNew: true, | ||
| 182 | - brand: '爱玛' | ||
| 183 | - }, | ||
| 184 | - { | ||
| 185 | - id: 4, | ||
| 186 | - name: '台铃 TDR-2024 电动车', | ||
| 187 | - year: '2024年', | ||
| 188 | - batteryHealth: 92, | ||
| 189 | - mileage: 800, | ||
| 190 | - price: 4200, | ||
| 191 | - school: '上海交通大学', | ||
| 192 | - imageUrl: 'https://images.unsplash.com/photo-1558618666-fcd25c85cd64?w=400&h=300&fit=crop', | ||
| 193 | - listingTime: '15分钟前上架', | ||
| 194 | - isNew: true, | ||
| 195 | - brand: '台铃' | ||
| 196 | - } | ||
| 197 | -]) | ||
| 198 | 151 | ||
| 199 | // 加载状态 | 152 | // 加载状态 |
| 200 | const loading = ref(false) | 153 | const loading = ref(false) |
| 201 | const hasMore = ref(true) | 154 | const hasMore = ref(true) |
| 202 | -const currentPage = ref(1) | 155 | +const currentPage = ref(0) |
| 203 | -const pageSize = ref(4) | 156 | +const pageSize = ref(10) |
| 204 | 157 | ||
| 205 | // Toast提示 | 158 | // Toast提示 |
| 206 | const toastVisible = ref(false) | 159 | const toastVisible = ref(false) |
| ... | @@ -266,79 +219,88 @@ const onSchoolChange = (value) => { | ... | @@ -266,79 +219,88 @@ const onSchoolChange = (value) => { |
| 266 | /** | 219 | /** |
| 267 | * 过滤车辆数据 | 220 | * 过滤车辆数据 |
| 268 | */ | 221 | */ |
| 269 | -const filterCars = () => { | 222 | +const filterCars = async () => { |
| 270 | - // TODO: 实现过滤逻辑 | 223 | + // 重置数据 |
| 224 | + newCars.value = [] | ||
| 225 | + currentPage.value = 0 | ||
| 226 | + hasMore.value = true | ||
| 227 | + | ||
| 228 | + // 重新加载数据 | ||
| 229 | + await loadVehicleData() | ||
| 271 | showToast('筛选条件已更新', 'success') | 230 | showToast('筛选条件已更新', 'success') |
| 272 | } | 231 | } |
| 273 | 232 | ||
| 274 | /** | 233 | /** |
| 275 | - * 生成模拟车辆数据 | 234 | + * 加载车辆数据 |
| 276 | - * @param {number} page - 页码 | 235 | + * @param {boolean} isLoadMore - 是否为加载更多 |
| 277 | - * @param {number} size - 每页数量 | ||
| 278 | - * @returns {Array} 车辆数据数组 | ||
| 279 | */ | 236 | */ |
| 280 | -const generateMockData = (page, size) => { | 237 | +const loadVehicleData = async (isLoadMore = false) => { |
| 281 | - const brands = ['雅迪', '台铃', '小鸟', '新日', '爱玛', '小牛', '绿源', '立马'] | 238 | + if (loading.value) return |
| 282 | - const schools = ['上海理工大学', '上海复旦大学', '上海同济大学', '上海交通大学', '华东师范大学', '上海大学'] | 239 | + |
| 283 | - const years = ['2024年', '2023年', '2022年', '2021年', '2020年'] | 240 | + loading.value = true |
| 284 | - const images = [ | 241 | + |
| 285 | - 'https://images.unsplash.com/photo-1567922045116-2a00fae2ed03?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | 242 | + try { |
| 286 | - 'https://images.unsplash.com/photo-1573981368236-719bbb6f70f7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | 243 | + // 构建请求参数 |
| 287 | - 'https://images.unsplash.com/photo-1583568671741-c70dafa8e8e7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | 244 | + const params = { |
| 288 | - 'https://images.unsplash.com/photo-1595941069915-4ebc5197c14a?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | 245 | + page: currentPage.value, |
| 289 | - 'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | 246 | + limit: pageSize.value |
| 290 | - 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60' | 247 | + } |
| 291 | - ] | 248 | + |
| 292 | - const listingTimes = ['刚刚上架', '5分钟前上架', '10分钟前上架', '15分钟前上架', '30分钟前上架', '1小时前上架'] | 249 | + // 添加筛选条件 |
| 293 | - | 250 | + if (selectedSchool.value) { |
| 294 | - const data = [] | 251 | + params.school_id = selectedSchool.value |
| 295 | - for (let i = 0; i < size; i++) { | 252 | + } |
| 296 | - const index = (page - 1) * size + i | 253 | + if (selectedBrand.value) { |
| 297 | - const brand = brands[Math.floor(Math.random() * brands.length)] | 254 | + params.brand = selectedBrand.value |
| 298 | - const school = schools[Math.floor(Math.random() * schools.length)] | 255 | + } |
| 299 | - const year = years[Math.floor(Math.random() * years.length)] | 256 | + if (selectedYear.value) { |
| 300 | - const image = images[Math.floor(Math.random() * images.length)] | 257 | + params.manufacture_year = selectedYear.value |
| 301 | - const listingTime = listingTimes[Math.floor(Math.random() * listingTimes.length)] | 258 | + } |
| 302 | - | 259 | + if (searchValue.value.trim()) { |
| 303 | - data.push({ | 260 | + params.keyword = searchValue.value.trim() |
| 304 | - id: `new_${index + 100}`, | 261 | + } |
| 305 | - name: `${brand} ${['豪华版', '标准版', '运动版', '经典版'][Math.floor(Math.random() * 4)]}`, | 262 | + |
| 306 | - year: year, | 263 | + const response = await getVehicleListAPI(params) |
| 307 | - school: school, | 264 | + |
| 308 | - price: Math.floor(Math.random() * 3000) + 3000, // 新车价格相对较高 | 265 | + if (response && response.code === 1 && response.data) { |
| 309 | - imageUrl: image, | 266 | + const vehicleList = response.data.list || [] |
| 310 | - batteryHealth: Math.floor(Math.random() * 10) + 90, // 新车电池健康度较高 | 267 | + |
| 311 | - mileage: Math.floor(Math.random() * 1000), // 新车里程较少 | 268 | + // 处理图片数据 |
| 312 | - brand: brand, | 269 | + const processedData = vehicleList.map(item => ({ |
| 313 | - listingTime: listingTime, | 270 | + ...item, |
| 314 | - isNew: Math.random() > 0.3 // 70%概率显示新标签 | 271 | + // 添加新车标签(7天内发布的车辆) |
| 315 | - }) | 272 | + isNew: new Date() - new Date(item.created_time) < 7 * 24 * 60 * 60 * 1000 |
| 273 | + })) | ||
| 274 | + | ||
| 275 | + if (isLoadMore) { | ||
| 276 | + newCars.value.push(...processedData) | ||
| 277 | + } else { | ||
| 278 | + newCars.value = processedData | ||
| 279 | + } | ||
| 280 | + | ||
| 281 | + // 检查是否还有更多数据 - 基于总数和当前已加载数量 | ||
| 282 | + const totalLoaded = (currentPage.value + 1) * pageSize.value | ||
| 283 | + hasMore.value = totalLoaded < response.data.total | ||
| 284 | + } else { | ||
| 285 | + console.error('API返回错误:', response) | ||
| 286 | + showToast(response?.msg || '获取数据失败', 'error') | ||
| 287 | + } | ||
| 288 | + } catch (error) { | ||
| 289 | + console.error('加载车辆数据失败:', error) | ||
| 290 | + showToast('网络错误,请稍后重试', 'error') | ||
| 291 | + } finally { | ||
| 292 | + loading.value = false | ||
| 316 | } | 293 | } |
| 317 | - return data | ||
| 318 | } | 294 | } |
| 319 | 295 | ||
| 320 | /** | 296 | /** |
| 321 | * 加载更多数据 | 297 | * 加载更多数据 |
| 322 | */ | 298 | */ |
| 323 | -const loadMore = () => { | 299 | +const loadMore = async () => { |
| 324 | if (loading.value || !hasMore.value) return | 300 | if (loading.value || !hasMore.value) return |
| 325 | 301 | ||
| 326 | - loading.value = true | 302 | + currentPage.value++ |
| 327 | - | 303 | + await loadVehicleData(true) |
| 328 | - // 模拟网络请求延迟 | ||
| 329 | - setTimeout(() => { | ||
| 330 | - // 模拟最多加载5页数据 | ||
| 331 | - if (currentPage.value >= 5) { | ||
| 332 | - hasMore.value = false | ||
| 333 | - loading.value = false | ||
| 334 | - return | ||
| 335 | - } | ||
| 336 | - | ||
| 337 | - currentPage.value++ | ||
| 338 | - const newData = generateMockData(currentPage.value, pageSize.value) | ||
| 339 | - newCars.value.push(...newData) | ||
| 340 | - loading.value = false | ||
| 341 | - }, 1000 + Math.random() * 1000) | ||
| 342 | } | 304 | } |
| 343 | 305 | ||
| 344 | /** | 306 | /** |
| ... | @@ -359,7 +321,6 @@ const showToast = (message, type = 'success') => { | ... | @@ -359,7 +321,6 @@ const showToast = (message, type = 'success') => { |
| 359 | 321 | ||
| 360 | // 初始化 | 322 | // 初始化 |
| 361 | onMounted(async () => { | 323 | onMounted(async () => { |
| 362 | - // 可以在这里加载初始数据 | ||
| 363 | // 获取全部品牌数据 | 324 | // 获取全部品牌数据 |
| 364 | const vBrands = await getVehicleBrandsAPI() | 325 | const vBrands = await getVehicleBrandsAPI() |
| 365 | if (vBrands.code) { | 326 | if (vBrands.code) { |
| ... | @@ -393,6 +354,9 @@ onMounted(async () => { | ... | @@ -393,6 +354,9 @@ onMounted(async () => { |
| 393 | text: '全部学校', | 354 | text: '全部学校', |
| 394 | value: '' | 355 | value: '' |
| 395 | }, ...schoolOptions.value] | 356 | }, ...schoolOptions.value] |
| 357 | + | ||
| 358 | + // 加载初始车辆数据 | ||
| 359 | + await loadVehicleData() | ||
| 396 | }) | 360 | }) |
| 397 | </script> | 361 | </script> |
| 398 | 362 | ... | ... |
-
Please register or login to post a comment