feat(我的收藏): 实现真实API调用并优化收藏列表展示
替换模拟数据为真实API调用,包括获取收藏列表和取消收藏功能 添加默认封面图片配置,优化列表项数据展示和处理 处理分页逻辑和错误情况,确保数据加载稳定性
Showing
3 changed files
with
82 additions
and
90 deletions
| 1 | +/* | ||
| 2 | + * @Date: 2025-07-10 16:13:08 | ||
| 3 | + * @LastEditors: hookehuyr hookehuyr@gmail.com | ||
| 4 | + * @LastEditTime: 2025-07-11 09:40:22 | ||
| 5 | + * @FilePath: /jgdl/src/api/other.js | ||
| 6 | + * @Description: 文件描述 | ||
| 7 | + */ | ||
| 1 | import { fn, fetch } from '@/api/fn'; | 8 | import { fn, fetch } from '@/api/fn'; |
| 2 | 9 | ||
| 3 | const Api = { | 10 | const Api = { |
| ... | @@ -33,6 +40,8 @@ export const getVehicleBrandsAPI = (params) => fn(fetch.get(Api.GET_VEHICLE_BRAN | ... | @@ -33,6 +40,8 @@ export const getVehicleBrandsAPI = (params) => fn(fetch.get(Api.GET_VEHICLE_BRAN |
| 33 | /** | 40 | /** |
| 34 | * @description: 获取收藏列表 | 41 | * @description: 获取收藏列表 |
| 35 | * @param {*} params | 42 | * @param {*} params |
| 43 | + * @param {number} params.page - 页码,从0开始 | ||
| 44 | + * @param {number} params.limit - 每页数量 | ||
| 36 | * @returns | 45 | * @returns |
| 37 | */ | 46 | */ |
| 38 | export const getFavoriteListAPI = (params) => fn(fetch.get(Api.GET_FAVORITE_LIST, params)); | 47 | export const getFavoriteListAPI = (params) => fn(fetch.get(Api.GET_FAVORITE_LIST, params)); | ... | ... |
| 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-09 11:35:02 | 4 | + * @LastEditTime: 2025-07-11 10:01:52 |
| 5 | * @FilePath: /jgdl/src/pages/myFavorites/index.vue | 5 | * @FilePath: /jgdl/src/pages/myFavorites/index.vue |
| 6 | * @Description: 我的关注页面 | 6 | * @Description: 我的关注页面 |
| 7 | --> | 7 | --> |
| ... | @@ -29,22 +29,21 @@ | ... | @@ -29,22 +29,21 @@ |
| 29 | <view class="flex p-4"> | 29 | <view class="flex p-4"> |
| 30 | <view class="w-24 h-24 relative"> | 30 | <view class="w-24 h-24 relative"> |
| 31 | <image | 31 | <image |
| 32 | - :src="item.imageUrl" | 32 | + :src="item.front_photo" |
| 33 | - :alt="item.name" | ||
| 34 | mode="aspectFill" | 33 | mode="aspectFill" |
| 35 | class="w-full h-full object-cover rounded-lg" | 34 | class="w-full h-full object-cover rounded-lg" |
| 36 | /> | 35 | /> |
| 37 | </view> | 36 | </view> |
| 38 | <view class="flex-1 ml-4"> | 37 | <view class="flex-1 ml-4"> |
| 39 | - <text class="font-medium text-base block">{{ item.name }}</text> | 38 | + <text class="font-medium text-base block">{{ item.brand }} {{ item.model }}</text> |
| 40 | - <text class="text-sm text-gray-500 mt-1 block">{{ item.details }}</text> | 39 | + <text class="text-sm text-gray-500 mt-1 block">{{ item.note }}</text> |
| 41 | <view class="mt-2 flex justify-between items-center"> | 40 | <view class="mt-2 flex justify-between items-center"> |
| 42 | <view> | 41 | <view> |
| 43 | <text class="text-orange-500 font-bold" style="font-size: 1.2rem;"> | 42 | <text class="text-orange-500 font-bold" style="font-size: 1.2rem;"> |
| 44 | ¥{{ item.price.toLocaleString() }} | 43 | ¥{{ item.price.toLocaleString() }} |
| 45 | </text> | 44 | </text> |
| 46 | - <text class="text-gray-400 text-xs line-through ml-2"> | 45 | + <text class="text-gray-400 text-xs line-through ml-2" v-if="item.market_price"> |
| 47 | - ¥{{ item.originalPrice.toLocaleString() }} | 46 | + ¥{{ item.market_price.toLocaleString() }} |
| 48 | </text> | 47 | </text> |
| 49 | </view> | 48 | </view> |
| 50 | <nut-button | 49 | <nut-button |
| ... | @@ -111,10 +110,13 @@ | ... | @@ -111,10 +110,13 @@ |
| 111 | import { ref, computed, onMounted } from 'vue' | 110 | import { ref, computed, onMounted } from 'vue' |
| 112 | import Taro from '@tarojs/taro' | 111 | import Taro from '@tarojs/taro' |
| 113 | import './index.less' | 112 | import './index.less' |
| 113 | +// 导入接口 | ||
| 114 | +import { getFavoriteListAPI, toggleFavoriteDelAPI } from '@/api/other' | ||
| 115 | +import { DEFAULT_COVER_IMG } from '@/utils/config' | ||
| 114 | 116 | ||
| 115 | // ==================== API相关 ==================== | 117 | // ==================== API相关 ==================== |
| 116 | /** | 118 | /** |
| 117 | - * API服务 - 为真实API预留空间 | 119 | + * API服务 - 使用真实API |
| 118 | */ | 120 | */ |
| 119 | const apiService = { | 121 | const apiService = { |
| 120 | /** | 122 | /** |
| ... | @@ -123,22 +125,16 @@ const apiService = { | ... | @@ -123,22 +125,16 @@ const apiService = { |
| 123 | * @param {number} pageSize - 每页数量 | 125 | * @param {number} pageSize - 每页数量 |
| 124 | * @returns {Promise} API响应 | 126 | * @returns {Promise} API响应 |
| 125 | */ | 127 | */ |
| 126 | - async getFavoritesList(page = 1, pageSize = 10) { | 128 | + async getFavoritesList(page = 0, pageSize = 10) { |
| 127 | - // TODO: 替换为真实API调用 | 129 | + try { |
| 128 | - // return await request.get('/api/favorites', { page, pageSize }) | 130 | + const params = { |
| 129 | - | 131 | + page: page, |
| 130 | - // 模拟API延迟 | 132 | + limit: pageSize |
| 131 | - await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 400)) | 133 | + } |
| 132 | - | 134 | + return await getFavoriteListAPI(params) |
| 133 | - // 模拟API响应数据 | 135 | + } catch (error) { |
| 134 | - return { | 136 | + console.error('获取收藏列表失败:', error) |
| 135 | - code: 1, | 137 | + throw error |
| 136 | - data: { | ||
| 137 | - list: generateMockData(page, pageSize), | ||
| 138 | - total: 50, // 模拟总数 | ||
| 139 | - hasMore: page < 5 // 模拟是否还有更多数据 | ||
| 140 | - }, | ||
| 141 | - message: 'success' | ||
| 142 | } | 138 | } |
| 143 | }, | 139 | }, |
| 144 | 140 | ||
| ... | @@ -148,17 +144,11 @@ const apiService = { | ... | @@ -148,17 +144,11 @@ const apiService = { |
| 148 | * @returns {Promise} API响应 | 144 | * @returns {Promise} API响应 |
| 149 | */ | 145 | */ |
| 150 | async unfollowCar(carId) { | 146 | async unfollowCar(carId) { |
| 151 | - // TODO: 替换为真实API调用 | 147 | + try { |
| 152 | - // return await request.delete(`/api/favorites/${carId}`) | 148 | + return await toggleFavoriteDelAPI({ vehicle_id: carId }) |
| 153 | - | 149 | + } catch (error) { |
| 154 | - // 模拟API延迟 | 150 | + console.error('取消关注失败:', error) |
| 155 | - await new Promise(resolve => setTimeout(resolve, 500)) | 151 | + throw error |
| 156 | - | ||
| 157 | - // 模拟API响应 | ||
| 158 | - return { | ||
| 159 | - code: 1, | ||
| 160 | - data: null, | ||
| 161 | - message: '取消关注成功' | ||
| 162 | } | 152 | } |
| 163 | } | 153 | } |
| 164 | } | 154 | } |
| ... | @@ -174,7 +164,7 @@ const favorites = ref([]) | ... | @@ -174,7 +164,7 @@ const favorites = ref([]) |
| 174 | */ | 164 | */ |
| 175 | const loading = ref(false) | 165 | const loading = ref(false) |
| 176 | const hasMore = ref(true) | 166 | const hasMore = ref(true) |
| 177 | -const currentPage = ref(1) | 167 | +const currentPage = ref(0) |
| 178 | const pageSize = ref(10) | 168 | const pageSize = ref(10) |
| 179 | 169 | ||
| 180 | /** | 170 | /** |
| ... | @@ -204,48 +194,7 @@ const scrollStyle = computed(() => { | ... | @@ -204,48 +194,7 @@ const scrollStyle = computed(() => { |
| 204 | }) | 194 | }) |
| 205 | 195 | ||
| 206 | // ==================== 数据处理方法 ==================== | 196 | // ==================== 数据处理方法 ==================== |
| 207 | -/** | 197 | + |
| 208 | - * 生成模拟数据 | ||
| 209 | - * @param {number} page - 页码 | ||
| 210 | - * @param {number} size - 每页数量 | ||
| 211 | - * @returns {Array} 模拟数据数组 | ||
| 212 | - */ | ||
| 213 | -const generateMockData = (page, size) => { | ||
| 214 | - const brands = ['小牛', '雅迪', '绿源', '爱玛', '台铃', '新日', '立马', '小鸟'] | ||
| 215 | - const models = ['豪华版', '标准版', '运动版', '经典版', '智能版', '动力版'] | ||
| 216 | - const images = [ | ||
| 217 | - 'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 218 | - 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 219 | - 'https://images.unsplash.com/photo-1591637333184-19aa84b3e01f?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 220 | - 'https://images.unsplash.com/photo-1558980664-3a031cf67ea8?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 221 | - 'https://images.unsplash.com/photo-1567922045116-2a00fae2ed03?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60', | ||
| 222 | - 'https://images.unsplash.com/photo-1573981368236-719bbb6f70f7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60' | ||
| 223 | - ] | ||
| 224 | - | ||
| 225 | - const data = [] | ||
| 226 | - for (let i = 0; i < size; i++) { | ||
| 227 | - const index = (page - 1) * size + i | ||
| 228 | - const brand = brands[Math.floor(Math.random() * brands.length)] | ||
| 229 | - const model = models[Math.floor(Math.random() * models.length)] | ||
| 230 | - const image = images[Math.floor(Math.random() * images.length)] | ||
| 231 | - const originalPrice = Math.floor(Math.random() * 3000) + 3000 | ||
| 232 | - const price = Math.floor(originalPrice * (0.7 + Math.random() * 0.2)) // 7-9折 | ||
| 233 | - const usageTime = Math.floor(Math.random() * 24) + 1 // 1-24个月 | ||
| 234 | - const range = Math.floor(Math.random() * 100) + 60 // 60-160km续航 | ||
| 235 | - | ||
| 236 | - data.push({ | ||
| 237 | - id: `fav_${index + 100}`, | ||
| 238 | - name: `${brand} ${model}`, | ||
| 239 | - details: `续航${range}km | 使用${usageTime}个月`, | ||
| 240 | - price: price, | ||
| 241 | - originalPrice: originalPrice, | ||
| 242 | - imageUrl: image, | ||
| 243 | - brand: brand, | ||
| 244 | - followTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString() // 最近30天内关注 | ||
| 245 | - }) | ||
| 246 | - } | ||
| 247 | - return data | ||
| 248 | -} | ||
| 249 | 198 | ||
| 250 | /** | 199 | /** |
| 251 | * 初始化加载数据 | 200 | * 初始化加载数据 |
| ... | @@ -253,13 +202,28 @@ const generateMockData = (page, size) => { | ... | @@ -253,13 +202,28 @@ const generateMockData = (page, size) => { |
| 253 | const initData = async () => { | 202 | const initData = async () => { |
| 254 | loading.value = true | 203 | loading.value = true |
| 255 | try { | 204 | try { |
| 256 | - const response = await apiService.getFavoritesList(1, pageSize.value) | 205 | + const response = await apiService.getFavoritesList(0, pageSize.value) |
| 257 | - if (response.code) { | 206 | + if (response && response.code === 1 && response.data) { |
| 258 | - favorites.value = response.data.list | 207 | + const vehicleList = response.data.list || [] |
| 259 | - hasMore.value = response.data.hasMore | 208 | + |
| 260 | - currentPage.value = 1 | 209 | + // 处理图片数据 |
| 210 | + const processedData = vehicleList.map(item => ({ | ||
| 211 | + ...item, | ||
| 212 | + front_photo: item.front_photo || DEFAULT_COVER_IMG, | ||
| 213 | + // 确保价格为数字类型 | ||
| 214 | + price: Number(item.price) || 0, | ||
| 215 | + market_price: Number(item.market_price) || 0 | ||
| 216 | + })) | ||
| 217 | + | ||
| 218 | + favorites.value = processedData | ||
| 219 | + | ||
| 220 | + // 检查是否还有更多数据 | ||
| 221 | + const totalLoaded = pageSize.value | ||
| 222 | + hasMore.value = totalLoaded < response.data.total | ||
| 223 | + currentPage.value = 0 | ||
| 261 | } else { | 224 | } else { |
| 262 | - showToast('加载失败,请重试', 'error') | 225 | + console.error('API返回错误:', response) |
| 226 | + showToast(response?.msg || '加载失败,请重试', 'error') | ||
| 263 | } | 227 | } |
| 264 | } catch (error) { | 228 | } catch (error) { |
| 265 | console.error('加载我的关注列表失败:', error) | 229 | console.error('加载我的关注列表失败:', error) |
| ... | @@ -280,12 +244,27 @@ const loadMore = async () => { | ... | @@ -280,12 +244,27 @@ const loadMore = async () => { |
| 280 | const nextPage = currentPage.value + 1 | 244 | const nextPage = currentPage.value + 1 |
| 281 | const response = await apiService.getFavoritesList(nextPage, pageSize.value) | 245 | const response = await apiService.getFavoritesList(nextPage, pageSize.value) |
| 282 | 246 | ||
| 283 | - if (response.code) { | 247 | + if (response && response.code === 1 && response.data) { |
| 284 | - favorites.value.push(...response.data.list) | 248 | + const vehicleList = response.data.list || [] |
| 285 | - hasMore.value = response.data.hasMore | 249 | + |
| 250 | + // 处理图片数据 | ||
| 251 | + const processedData = vehicleList.map(item => ({ | ||
| 252 | + ...item, | ||
| 253 | + front_photo: item.front_photo || DEFAULT_COVER_IMG, | ||
| 254 | + // 确保价格为数字类型 | ||
| 255 | + price: Number(item.price) || 0, | ||
| 256 | + market_price: Number(item.market_price) || 0 | ||
| 257 | + })) | ||
| 258 | + | ||
| 259 | + favorites.value.push(...processedData) | ||
| 260 | + | ||
| 261 | + // 检查是否还有更多数据 | ||
| 262 | + const totalLoaded = (nextPage + 1) * pageSize.value | ||
| 263 | + hasMore.value = totalLoaded < response.data.total | ||
| 286 | currentPage.value = nextPage | 264 | currentPage.value = nextPage |
| 287 | } else { | 265 | } else { |
| 288 | - showToast('加载失败,请重试', 'error') | 266 | + console.error('API返回错误:', response) |
| 267 | + showToast(response?.msg || '加载失败,请重试', 'error') | ||
| 289 | } | 268 | } |
| 290 | } catch (error) { | 269 | } catch (error) { |
| 291 | console.error('加载更多数据失败:', error) | 270 | console.error('加载更多数据失败:', error) |
| ... | @@ -325,12 +304,13 @@ const confirmUnfollow = async () => { | ... | @@ -325,12 +304,13 @@ const confirmUnfollow = async () => { |
| 325 | try { | 304 | try { |
| 326 | const response = await apiService.unfollowCar(selectedId.value) | 305 | const response = await apiService.unfollowCar(selectedId.value) |
| 327 | 306 | ||
| 328 | - if (response.code) { | 307 | + if (response && response.code === 1) { |
| 329 | // 从列表中移除该项 | 308 | // 从列表中移除该项 |
| 330 | favorites.value = favorites.value.filter(item => item.id !== selectedId.value) | 309 | favorites.value = favorites.value.filter(item => item.id !== selectedId.value) |
| 331 | showToast('取消关注成功', 'success') | 310 | showToast('取消关注成功', 'success') |
| 332 | } else { | 311 | } else { |
| 333 | - showToast(response.message || '取消关注失败', 'error') | 312 | + console.error('取消关注失败:', response) |
| 313 | + showToast(response?.msg || '取消关注失败', 'error') | ||
| 334 | } | 314 | } |
| 335 | } catch (error) { | 315 | } catch (error) { |
| 336 | console.error('取消关注失败:', error) | 316 | console.error('取消关注失败:', error) | ... | ... |
| ... | @@ -10,3 +10,6 @@ const BASE_URL = "https://oa-dev.onwall.cn"; // 测试服务器 | ... | @@ -10,3 +10,6 @@ const BASE_URL = "https://oa-dev.onwall.cn"; // 测试服务器 |
| 10 | // const BASE_URL = "https://oa.onwall.cn"; // 正式服务器 | 10 | // const BASE_URL = "https://oa.onwall.cn"; // 正式服务器 |
| 11 | 11 | ||
| 12 | export default BASE_URL | 12 | export default BASE_URL |
| 13 | + | ||
| 14 | +// 默认封面图片地址 | ||
| 15 | +export const DEFAULT_COVER_IMG = 'https://images.unsplash.com/photo-1558981806-ec527fa84c39?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60' | ... | ... |
-
Please register or login to post a comment