feat(首页): 更新电动车列表数据结构和API调用
重构首页电动车展示逻辑,使用新的API返回数据结构: 1. 更新图片字段为front_photo 2. 使用brand和model组合显示电动车名称 3. 修改收藏功能直接操作scooter对象 4. 添加获取轮播图、精品推荐和最新上架数据的API调用 5. 更新认证状态判断逻辑为verification_status === 5
Showing
2 changed files
with
51 additions
and
47 deletions
| 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-09 16:08:53 | 4 | + * @LastEditTime: 2025-07-10 18:30:25 |
| 5 | * @FilePath: /jgdl/src/pages/index/index.vue | 5 | * @FilePath: /jgdl/src/pages/index/index.vue |
| 6 | * @Description: 捡个电驴首页 | 6 | * @Description: 捡个电驴首页 |
| 7 | --> | 7 | --> |
| ... | @@ -73,24 +73,24 @@ | ... | @@ -73,24 +73,24 @@ |
| 73 | <view v-for="scooter in featuredScooters" :key="scooter.id" | 73 | <view v-for="scooter in featuredScooters" :key="scooter.id" |
| 74 | class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)"> | 74 | class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)"> |
| 75 | <view class="relative p-2"> | 75 | <view class="relative p-2"> |
| 76 | - <image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill" | 76 | + <image :src="scooter.front_photo" mode="aspectFill" |
| 77 | class="w-full h-36 object-cover rounded-lg" /> | 77 | class="w-full h-36 object-cover rounded-lg" /> |
| 78 | - <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.id)"> | 78 | + <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)"> |
| 79 | - <Heart1 v-if="!favoriteIds.includes(scooter.id)" size="22" :color="'#9ca3af'" /> | 79 | + <Heart1 v-if="!scooter.is_favorite" size="22" :color="'#9ca3af'" /> |
| 80 | <HeartFill v-else size="22" :color="'#ef4444'" /> | 80 | <HeartFill v-else size="22" :color="'#ef4444'" /> |
| 81 | </view> | 81 | </view> |
| 82 | - <view v-if="scooter.isVerified" | 82 | + <view v-if="scooter.verification_status === 5" |
| 83 | class="absolute bottom-4 right-4 text-white text-xs px-1.5 py-0.5 rounded flex items-center" style="background-color: #EB5305;"> | 83 | class="absolute bottom-4 right-4 text-white text-xs px-1.5 py-0.5 rounded flex items-center" style="background-color: #EB5305;"> |
| 84 | <Check size="12" color="#ffffff" class="mr-0.5" /> | 84 | <Check size="12" color="#ffffff" class="mr-0.5" /> |
| 85 | <text class="text-white">认证</text> | 85 | <text class="text-white">认证</text> |
| 86 | </view> | 86 | </view> |
| 87 | </view> | 87 | </view> |
| 88 | <view class="p-2 pl-3"> | 88 | <view class="p-2 pl-3"> |
| 89 | - <text class="font-medium text-sm block">{{ scooter.name }}</text> | 89 | + <text class="font-medium text-sm block">{{ scooter.brand }} {{ scooter.model }}</text> |
| 90 | <text class="text-xs text-gray-500 block mt-1 mb-1"> | 90 | <text class="text-xs text-gray-500 block mt-1 mb-1"> |
| 91 | - {{ scooter.year }} · {{ scooter.school }} | 91 | + {{ scooter.manufacture_year }}年 · {{ scooter.school_name }} |
| 92 | - <text v-if="scooter.batteryHealth">电池健康度{{ scooter.batteryHealth }}%</text> | 92 | + <!-- <text v-if="scooter.battery_capacity_ah">电池容量{{ scooter.battery_capacity_ah }}Ah</text> |
| 93 | - <text v-if="scooter.mileage"> 行驶{{ scooter.mileage }}公里</text> | 93 | + <text v-if="scooter.total_mileage_km"> 行驶{{ scooter.total_mileage_km }}公里</text> --> |
| 94 | </text> | 94 | </text> |
| 95 | <view class="mt-1"> | 95 | <view class="mt-1"> |
| 96 | <text class="text-orange-500 font-bold" style="font-size: 1.25rem;"> | 96 | <text class="text-orange-500 font-bold" style="font-size: 1.25rem;"> |
| ... | @@ -118,32 +118,32 @@ | ... | @@ -118,32 +118,32 @@ |
| 118 | <view class="flex flex-col"> | 118 | <view class="flex flex-col"> |
| 119 | <view v-for="scooter in latestScooters" :key="scooter.id" | 119 | <view v-for="scooter in latestScooters" :key="scooter.id" |
| 120 | class="bg-white rounded-lg shadow-sm overflow-hidden mb-3" @tap="() => onProductClick(scooter)"> | 120 | class="bg-white rounded-lg shadow-sm overflow-hidden mb-3" @tap="() => onProductClick(scooter)"> |
| 121 | - <view class="flex"> | 121 | + <view class="flex min-h-32"> |
| 122 | - <view class="w-32 h-24 relative p-2"> | 122 | + <view class="w-32 relative p-2 flex flex-col"> |
| 123 | - <image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill" | 123 | + <image :src="scooter.front_photo" :alt="scooter.name" mode="aspectFill" |
| 124 | - class="w-full h-full object-cover rounded-lg" /> | 124 | + class="w-full flex-1 object-cover rounded-lg" /> |
| 125 | - <view v-if="scooter.isVerified" | 125 | + <view v-if="scooter.verification_status === 5" |
| 126 | class="absolute bottom-3 right-3 text-white text-xs px-1 rounded flex items-center" style="background-color: #EB5305;"> | 126 | class="absolute bottom-3 right-3 text-white text-xs px-1 rounded flex items-center" style="background-color: #EB5305;"> |
| 127 | <Check size="12" color="#ffffff" class="mr-0.5" /> | 127 | <Check size="12" color="#ffffff" class="mr-0.5" /> |
| 128 | <text class="text-white">认证</text> | 128 | <text class="text-white">认证</text> |
| 129 | </view> | 129 | </view> |
| 130 | </view> | 130 | </view> |
| 131 | - <view class="flex-1 p-3 relative"> | 131 | + <view class="flex-1 p-3 relative flex flex-col"> |
| 132 | - <view class="absolute top-2 right-2" @tap.stop="() => toggleFavorite(scooter.id)"> | 132 | + <view class="absolute top-2 right-2" @tap.stop="() => toggleFavorite(scooter)"> |
| 133 | - <Heart1 v-if="!favoriteIds.includes(scooter.id)" size="22" :color="'#9ca3af'" /> | 133 | + <Heart1 v-if="!scooter.is_favorite" size="22" :color="'#9ca3af'" /> |
| 134 | <HeartFill v-else size="22" :color="'#ef4444'" /> | 134 | <HeartFill v-else size="22" :color="'#ef4444'" /> |
| 135 | </view> | 135 | </view> |
| 136 | - <text class="font-medium text-sm block">{{ scooter.name }}</text> | 136 | + <text class="font-medium text-sm block">{{ scooter.brand }} {{ scooter.model }}</text> |
| 137 | - <text class="text-xs text-gray-600 mt-1 block"> | 137 | + <text class="text-xs text-gray-600 mt-1 block" style="word-break: break-all;"> |
| 138 | - {{ scooter.year }} · | 138 | + {{ scooter.manufacture_year }}年 |
| 139 | - <text v-if="scooter.batteryHealth">电池健康度{{ scooter.batteryHealth }}%</text> | 139 | + <text v-if="scooter.range_km">续航{{ scooter.range_km }}km</text> |
| 140 | - <text v-if="scooter.mileage"> 行驶{{ scooter.mileage }}公里</text> | 140 | + <text v-if="scooter.max_speed_kmh"> 最高时速{{ scooter.max_speed_kmh }}km/h</text> |
| 141 | </text> | 141 | </text> |
| 142 | - <view class="mt-2"> | 142 | + <view class="mt-auto"> |
| 143 | <text class="text-orange-500 font-bold" style="font-size: 1.25rem;"> | 143 | <text class="text-orange-500 font-bold" style="font-size: 1.25rem;"> |
| 144 | ¥{{ scooter.price.toLocaleString() }} | 144 | ¥{{ scooter.price.toLocaleString() }} |
| 145 | </text> | 145 | </text> |
| 146 | - <text class="text-xs text-gray-500 mt-1 block">{{ scooter.school }}</text> | 146 | + <text class="text-xs text-gray-500 mt-1 block">{{ scooter.school_name }}</text> |
| 147 | </view> | 147 | </view> |
| 148 | </view> | 148 | </view> |
| 149 | </view> | 149 | </view> |
| ... | @@ -168,7 +168,7 @@ import TabBar from '@/components/TabBar.vue' | ... | @@ -168,7 +168,7 @@ import TabBar from '@/components/TabBar.vue' |
| 168 | import SearchPopup from '@/components/SearchPopup.vue' | 168 | import SearchPopup from '@/components/SearchPopup.vue' |
| 169 | import "./index.less"; | 169 | import "./index.less"; |
| 170 | // 导入接口 | 170 | // 导入接口 |
| 171 | -import { getRecommendVehicleAPI } from '@/api/car'; | 171 | +import { getRecommendVehicleAPI, getVehicleListAPI } from '@/api/car'; |
| 172 | 172 | ||
| 173 | // 响应式数据 | 173 | // 响应式数据 |
| 174 | const searchValue = ref('') | 174 | const searchValue = ref('') |
| ... | @@ -181,11 +181,7 @@ const onSearchHandle = () => { | ... | @@ -181,11 +181,7 @@ const onSearchHandle = () => { |
| 181 | } | 181 | } |
| 182 | 182 | ||
| 183 | // Banner图片 | 183 | // Banner图片 |
| 184 | -const bannerImages = ref([ | 184 | +const bannerImages = ref([]) |
| 185 | - 'https://images.unsplash.com/photo-1558981806-ec527fa84c39?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 186 | - 'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 187 | - 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60' | ||
| 188 | -]) | ||
| 189 | 185 | ||
| 190 | // 精品推荐数据 | 186 | // 精品推荐数据 |
| 191 | const featuredScooters = ref([ | 187 | const featuredScooters = ref([ |
| ... | @@ -276,20 +272,18 @@ const latestScooters = ref([ | ... | @@ -276,20 +272,18 @@ const latestScooters = ref([ |
| 276 | * 切换收藏状态 | 272 | * 切换收藏状态 |
| 277 | * @param {string} scooterId - 电动车ID | 273 | * @param {string} scooterId - 电动车ID |
| 278 | */ | 274 | */ |
| 279 | -const toggleFavorite = (scooterId) => { | 275 | +const toggleFavorite = async (scooter) => { |
| 280 | - const index = favoriteIds.value.indexOf(scooterId) | 276 | + scooter.is_favorite = !scooter.is_favorite |
| 281 | - if (index > -1) { | 277 | + if (scooter.is_favorite) { |
| 282 | - favoriteIds.value.splice(index, 1) | ||
| 283 | Taro.showToast({ | 278 | Taro.showToast({ |
| 284 | - title: '取消收藏', | 279 | + title: '收藏成功', |
| 285 | - icon: 'none', | 280 | + icon: 'success', |
| 286 | duration: 2000 | 281 | duration: 2000 |
| 287 | }) | 282 | }) |
| 288 | } else { | 283 | } else { |
| 289 | - favoriteIds.value.push(scooterId) | ||
| 290 | Taro.showToast({ | 284 | Taro.showToast({ |
| 291 | - title: '收藏成功', | 285 | + title: '取消收藏', |
| 292 | - icon: 'success', | 286 | + icon: 'none', |
| 293 | duration: 2000 | 287 | duration: 2000 |
| 294 | }) | 288 | }) |
| 295 | } | 289 | } |
| ... | @@ -383,12 +377,21 @@ useReady(async () => { | ... | @@ -383,12 +377,21 @@ useReady(async () => { |
| 383 | }) | 377 | }) |
| 384 | 378 | ||
| 385 | onMounted(async () => { | 379 | onMounted(async () => { |
| 386 | - console.warn('index mounted') | 380 | + // 获取首页轮播 |
| 387 | - // 获取推荐车源 | 381 | + const res1 = await getRecommendVehicleAPI({ section: 1 }) |
| 388 | - // const res = await getRecommendVehicleAPI() | 382 | + if (res1.code) { |
| 389 | - // if (res.code) { | 383 | + bannerImages.value = res1.data.map(item => item.front_photo); |
| 390 | - // recommendVehicleList.value = res.data | 384 | + } |
| 391 | - // } | 385 | + // 获取精品推荐 |
| 386 | + const res2 = await getRecommendVehicleAPI({ section: 3 }) | ||
| 387 | + if (res2.code) { | ||
| 388 | + featuredScooters.value = res2.data | ||
| 389 | + } | ||
| 390 | + // 获取最新上架 | ||
| 391 | + const res3 = await getVehicleListAPI({ page: 0, page_size: 5 }) | ||
| 392 | + if (res3.code) { | ||
| 393 | + latestScooters.value = res3.data.list | ||
| 394 | + } | ||
| 392 | }) | 395 | }) |
| 393 | 396 | ||
| 394 | // 分享功能 | 397 | // 分享功能 | ... | ... |
| 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 17:40:19 | 4 | + * @LastEditTime: 2025-07-10 17:47:49 |
| 5 | * @FilePath: /jgdl/src/pages/myCar/index.vue | 5 | * @FilePath: /jgdl/src/pages/myCar/index.vue |
| 6 | * @Description: 文件描述 | 6 | * @Description: 文件描述 |
| 7 | --> | 7 | --> |
| ... | @@ -212,8 +212,9 @@ const confirmOffline = async () => { | ... | @@ -212,8 +212,9 @@ const confirmOffline = async () => { |
| 212 | } | 212 | } |
| 213 | 213 | ||
| 214 | // 调用API更新车源状态 | 214 | // 调用API更新车源状态 |
| 215 | - const { code, msg } = await changeVehicleStatusAPI({ id: car.id, status: status }) | 215 | + const { code, msg, data } = await changeVehicleStatusAPI({ id: car.id, status: status }) |
| 216 | if (code) { | 216 | if (code) { |
| 217 | + car.status = data.status; | ||
| 217 | Taro.showToast({ | 218 | Taro.showToast({ |
| 218 | title: status === 5 ? '下架成功' : msg, | 219 | title: status === 5 ? '下架成功' : msg, |
| 219 | icon: 'none', | 220 | icon: 'none', | ... | ... |
-
Please register or login to post a comment