hookehuyr

feat(我的收藏): 实现真实API调用并优化收藏列表展示

替换模拟数据为真实API调用,包括获取收藏列表和取消收藏功能
添加默认封面图片配置,优化列表项数据展示和处理
处理分页逻辑和错误情况,确保数据加载稳定性
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'
......