hookehuyr

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

替换模拟数据为真实API调用,包括获取收藏列表和取消收藏功能
添加默认封面图片配置,优化列表项数据展示和处理
处理分页逻辑和错误情况,确保数据加载稳定性
/*
* @Date: 2025-07-10 16:13:08
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-11 09:40:22
* @FilePath: /jgdl/src/api/other.js
* @Description: 文件描述
*/
import { fn, fetch } from '@/api/fn';
const Api = {
......@@ -33,6 +40,8 @@ export const getVehicleBrandsAPI = (params) => fn(fetch.get(Api.GET_VEHICLE_BRAN
/**
* @description: 获取收藏列表
* @param {*} params
* @param {number} params.page - 页码,从0开始
* @param {number} params.limit - 每页数量
* @returns
*/
export const getFavoriteListAPI = (params) => fn(fetch.get(Api.GET_FAVORITE_LIST, params));
......
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-09 11:35:02
* @LastEditTime: 2025-07-11 10:01:52
* @FilePath: /jgdl/src/pages/myFavorites/index.vue
* @Description: 我的关注页面
-->
......@@ -29,22 +29,21 @@
<view class="flex p-4">
<view class="w-24 h-24 relative">
<image
:src="item.imageUrl"
:alt="item.name"
:src="item.front_photo"
mode="aspectFill"
class="w-full h-full object-cover rounded-lg"
/>
</view>
<view class="flex-1 ml-4">
<text class="font-medium text-base block">{{ item.name }}</text>
<text class="text-sm text-gray-500 mt-1 block">{{ item.details }}</text>
<text class="font-medium text-base block">{{ item.brand }} {{ item.model }}</text>
<text class="text-sm text-gray-500 mt-1 block">{{ item.note }}</text>
<view class="mt-2 flex justify-between items-center">
<view>
<text class="text-orange-500 font-bold" style="font-size: 1.2rem;">
¥{{ item.price.toLocaleString() }}
</text>
<text class="text-gray-400 text-xs line-through ml-2">
¥{{ item.originalPrice.toLocaleString() }}
<text class="text-gray-400 text-xs line-through ml-2" v-if="item.market_price">
¥{{ item.market_price.toLocaleString() }}
</text>
</view>
<nut-button
......@@ -111,10 +110,13 @@
import { ref, computed, onMounted } from 'vue'
import Taro from '@tarojs/taro'
import './index.less'
// 导入接口
import { getFavoriteListAPI, toggleFavoriteDelAPI } from '@/api/other'
import { DEFAULT_COVER_IMG } from '@/utils/config'
// ==================== API相关 ====================
/**
* API服务 - 为真实API预留空间
* API服务 - 使用真实API
*/
const apiService = {
/**
......@@ -123,22 +125,16 @@ const apiService = {
* @param {number} pageSize - 每页数量
* @returns {Promise} API响应
*/
async getFavoritesList(page = 1, pageSize = 10) {
// TODO: 替换为真实API调用
// return await request.get('/api/favorites', { page, pageSize })
// 模拟API延迟
await new Promise(resolve => setTimeout(resolve, 800 + Math.random() * 400))
// 模拟API响应数据
return {
code: 1,
data: {
list: generateMockData(page, pageSize),
total: 50, // 模拟总数
hasMore: page < 5 // 模拟是否还有更多数据
},
message: 'success'
async getFavoritesList(page = 0, pageSize = 10) {
try {
const params = {
page: page,
limit: pageSize
}
return await getFavoriteListAPI(params)
} catch (error) {
console.error('获取收藏列表失败:', error)
throw error
}
},
......@@ -148,17 +144,11 @@ const apiService = {
* @returns {Promise} API响应
*/
async unfollowCar(carId) {
// TODO: 替换为真实API调用
// return await request.delete(`/api/favorites/${carId}`)
// 模拟API延迟
await new Promise(resolve => setTimeout(resolve, 500))
// 模拟API响应
return {
code: 1,
data: null,
message: '取消关注成功'
try {
return await toggleFavoriteDelAPI({ vehicle_id: carId })
} catch (error) {
console.error('取消关注失败:', error)
throw error
}
}
}
......@@ -174,7 +164,7 @@ const favorites = ref([])
*/
const loading = ref(false)
const hasMore = ref(true)
const currentPage = ref(1)
const currentPage = ref(0)
const pageSize = ref(10)
/**
......@@ -204,48 +194,7 @@ const scrollStyle = computed(() => {
})
// ==================== 数据处理方法 ====================
/**
* 生成模拟数据
* @param {number} page - 页码
* @param {number} size - 每页数量
* @returns {Array} 模拟数据数组
*/
const generateMockData = (page, size) => {
const brands = ['小牛', '雅迪', '绿源', '爱玛', '台铃', '新日', '立马', '小鸟']
const models = ['豪华版', '标准版', '运动版', '经典版', '智能版', '动力版']
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-1567922045116-2a00fae2ed03?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
'https://images.unsplash.com/photo-1573981368236-719bbb6f70f7?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
]
const data = []
for (let i = 0; i < size; i++) {
const index = (page - 1) * size + i
const brand = brands[Math.floor(Math.random() * brands.length)]
const model = models[Math.floor(Math.random() * models.length)]
const image = images[Math.floor(Math.random() * images.length)]
const originalPrice = Math.floor(Math.random() * 3000) + 3000
const price = Math.floor(originalPrice * (0.7 + Math.random() * 0.2)) // 7-9折
const usageTime = Math.floor(Math.random() * 24) + 1 // 1-24个月
const range = Math.floor(Math.random() * 100) + 60 // 60-160km续航
data.push({
id: `fav_${index + 100}`,
name: `${brand} ${model}`,
details: `续航${range}km | 使用${usageTime}个月`,
price: price,
originalPrice: originalPrice,
imageUrl: image,
brand: brand,
followTime: new Date(Date.now() - Math.random() * 30 * 24 * 60 * 60 * 1000).toISOString() // 最近30天内关注
})
}
return data
}
/**
* 初始化加载数据
......@@ -253,13 +202,28 @@ const generateMockData = (page, size) => {
const initData = async () => {
loading.value = true
try {
const response = await apiService.getFavoritesList(1, pageSize.value)
if (response.code) {
favorites.value = response.data.list
hasMore.value = response.data.hasMore
currentPage.value = 1
const response = await apiService.getFavoritesList(0, pageSize.value)
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,
// 确保价格为数字类型
price: Number(item.price) || 0,
market_price: Number(item.market_price) || 0
}))
favorites.value = processedData
// 检查是否还有更多数据
const totalLoaded = pageSize.value
hasMore.value = totalLoaded < response.data.total
currentPage.value = 0
} else {
showToast('加载失败,请重试', 'error')
console.error('API返回错误:', response)
showToast(response?.msg || '加载失败,请重试', 'error')
}
} catch (error) {
console.error('加载我的关注列表失败:', error)
......@@ -280,12 +244,27 @@ const loadMore = async () => {
const nextPage = currentPage.value + 1
const response = await apiService.getFavoritesList(nextPage, pageSize.value)
if (response.code) {
favorites.value.push(...response.data.list)
hasMore.value = response.data.hasMore
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,
// 确保价格为数字类型
price: Number(item.price) || 0,
market_price: Number(item.market_price) || 0
}))
favorites.value.push(...processedData)
// 检查是否还有更多数据
const totalLoaded = (nextPage + 1) * pageSize.value
hasMore.value = totalLoaded < response.data.total
currentPage.value = nextPage
} else {
showToast('加载失败,请重试', 'error')
console.error('API返回错误:', response)
showToast(response?.msg || '加载失败,请重试', 'error')
}
} catch (error) {
console.error('加载更多数据失败:', error)
......@@ -325,12 +304,13 @@ const confirmUnfollow = async () => {
try {
const response = await apiService.unfollowCar(selectedId.value)
if (response.code) {
if (response && response.code === 1) {
// 从列表中移除该项
favorites.value = favorites.value.filter(item => item.id !== selectedId.value)
showToast('取消关注成功', 'success')
} else {
showToast(response.message || '取消关注失败', 'error')
console.error('取消关注失败:', response)
showToast(response?.msg || '取消关注失败', 'error')
}
} catch (error) {
console.error('取消关注失败:', error)
......
......@@ -10,3 +10,6 @@ const BASE_URL = "https://oa-dev.onwall.cn"; // 测试服务器
// const BASE_URL = "https://oa.onwall.cn"; // 正式服务器
export default BASE_URL
// 默认封面图片地址
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'
......