hookehuyr

feat(收藏): 重构收藏功能为基于对象属性的模式并添加API支持

添加收藏相关API接口
创建useFavorite composable统一管理收藏状态
移除各页面中的favoriteIds数组,改用对象is_favorite属性
更新所有页面使用新的收藏逻辑
......@@ -4,6 +4,9 @@ const Api = {
GET_SCHOOLS: '/srv/?a=common&t=get_schools',
GET_BRANDS_MODELS: '/srv/?a=common&t=get_brands_models',
GET_VEHICLE_BRANDS: '/srv/?a=common&t=get_vehicle_brands',
GET_FAVORITE_LIST: '/srv/?a=favorite&t=list',
TOGGLE_FAVORITE_ADD: '/srv/?a=favorite&t=add',
TOGGLE_FAVORITE_DEL: '/srv/?a=favorite&t=del',
}
/**
......@@ -26,3 +29,18 @@ export const getBrandsModelsAPI = (params) => fn(fetch.get(Api.GET_BRANDS_MODELS
* @returns
*/
export const getVehicleBrandsAPI = (params) => fn(fetch.get(Api.GET_VEHICLE_BRANDS, params));
/**
* @description: 获取收藏列表
* @param {*} params
* @returns
*/
export const getFavoriteListAPI = (params) => fn(fetch.get(Api.GET_FAVORITE_LIST, params));
/**
* @description: 切换收藏
* @param {*} params
* @returns
*/
export const toggleFavoriteAddAPI = (params) => fn(fetch.post(Api.TOGGLE_FAVORITE_ADD, params));
export const toggleFavoriteDelAPI = (params) => fn(fetch.post(Api.TOGGLE_FAVORITE_DEL, params));
......
......@@ -65,8 +65,8 @@
<view class="relative p-2">
<image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill"
class="w-full h-36 object-cover rounded-lg" />
<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)">
<Heart1 v-if="!favoriteIds.includes(scooter.id)" size="22" :color="'#9ca3af'" />
<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)">
<Heart1 v-if="!scooter.is_favorite" size="22" :color="'#9ca3af'" />
<HeartFill v-else size="22" :color="'#ef4444'" />
</view>
<view v-if="scooter.isVerified"
......@@ -131,6 +131,7 @@
import { ref, computed, watch } from 'vue'
import Taro from '@tarojs/taro'
import { Search2, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { useFavorite } from '@/composables/useFavorite'
import "./SearchPopup.less"
// Props
......@@ -152,7 +153,7 @@ const visible = computed({
// 响应式数据
const searchValue = ref('')
const favoriteIds = ref(['5', '7', '1'])
const hasSearched = ref(false)
// 滚动相关
......@@ -356,28 +357,8 @@ const loadMore = () => {
}, 500)
}
/**
* 切换收藏状态
* @param {string} id 商品ID
*/
const toggleFavorite = (id) => {
const index = favoriteIds.value.indexOf(id)
if (index > -1) {
favoriteIds.value.splice(index, 1)
Taro.showToast({
title: '取消收藏',
icon: 'none',
duration: 2000
})
} else {
favoriteIds.value.push(id)
Taro.showToast({
title: '收藏成功',
icon: 'success',
duration: 2000
})
}
}
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
/**
* 商品点击事件
......
/*
* @Date: 2025-07-10 21:28:57
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-10 21:59:52
* @FilePath: /jgdl/src/composables/useFavorite.js
* @Description: 文件描述
*/
import Taro from '@tarojs/taro'
import { toggleFavoriteAddAPI, toggleFavoriteDelAPI } from '@/api/other';
/**
* 收藏功能的composables - 基于对象属性的模式
* 支持对象的is_favorite属性进行收藏状态管理
*/
export function useFavorite() {
/**
* 切换收藏状态
* @param {Object} item - 包含is_favorite属性的对象
*/
const toggleFavorite = async (item) => {
if (!item.is_favorite) {
const { code } = await toggleFavoriteAddAPI({
vehicle_id: item.id,
})
if (code) {
item.is_favorite = !item.is_favorite
Taro.showToast({
title: '收藏成功',
icon: 'success',
duration: 2000
})
}
} else {
const { code } = await toggleFavoriteDelAPI({
vehicle_id: item.id,
})
if (code) {
item.is_favorite = !item.is_favorite
Taro.showToast({
title: '取消收藏',
icon: 'none',
duration: 2000
})
}
}
}
/**
* 检查是否已收藏
* @param {Object} item - 包含is_favorite属性的对象
* @returns {boolean} 是否已收藏
*/
const isFavorite = (item) => {
return item.is_favorite || false
}
return {
toggleFavorite,
isFavorite
}
}
// 设置useFavorite为默认导出
export default useFavorite
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-06 18:13:43
* @LastEditTime: 2025-07-10 21:34:57
* @FilePath: /jgdl/src/pages/authCar/index.vue
* @Description: 认证车源
-->
......@@ -60,8 +60,8 @@
class="w-full h-full object-cover rounded-lg" />
</view>
<view class="flex-1 p-3 relative">
<view class="absolute top-2 right-4" @tap.stop="() => toggleFavorite(car.id)">
<Heart1 v-if="!favoriteIds.includes(car.id)" size="22" :color="'#9ca3af'" />
<view class="absolute top-2 right-4" @tap.stop="() => toggleFavorite(car)">
<Heart1 v-if="!car.is_favorite" size="22" :color="'#9ca3af'" />
<HeartFill v-else size="22" :color="'#ef4444'" />
</view>
<text class="font-medium text-sm block">{{ car.name }}</text>
......@@ -109,7 +109,8 @@
<script setup>
import Taro from '@tarojs/taro'
import { ref, computed, onMounted } from 'vue'
import { Check, Addfollow, Follow, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { useFavorite } from '@/composables/useFavorite'
import './index.less'
// Banner图片数据
......@@ -168,7 +169,7 @@ const authCars = ref([
])
// 收藏状态
const favoriteIds = ref([2, 4])
// 收藏功能现在使用基于对象属性的模式
// 加载状态
const loading = ref(false)
......@@ -205,24 +206,8 @@ const onCarClick = (car) => {
/**
* 切换收藏状态
*/
const toggleFavorite = (carId) => {
const index = favoriteIds.value.indexOf(carId)
if (index > -1) {
favoriteIds.value.splice(index, 1)
Taro.showToast({
title: '取消收藏',
icon: 'none',
duration: 2000
})
} else {
favoriteIds.value.push(carId)
Taro.showToast({
title: '收藏成功',
icon: 'success',
duration: 2000
})
}
}
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
/**
* 加载更多数据
......@@ -268,11 +253,7 @@ const scroll = (e) => {
/**
* 显示提示信息
*/
const showToast = (message, type = 'success') => {
toastMessage.value = message
toastType.value = type
toastVisible.value = true
}
// showToast 功能现在由 useFavorite composable 处理
// 初始化
onMounted(() => {
......
......@@ -66,8 +66,8 @@
</view>
</view>
<view class="flex-1 p-3 relative">
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)">
<Heart1 v-if="!favoriteIds.includes(car.id)" size="22" color="#9ca3af" />
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car)">
<Heart1 v-if="!car.is_favorite" size="22" color="#9ca3af" />
<HeartFill v-else size="22" color="#ef4444" />
</view>
<text class="font-medium text-sm block">{{ car.name }}</text>
......@@ -125,8 +125,8 @@
<script setup>
import Taro from '@tarojs/taro'
import { ref, computed, onMounted } from 'vue'
import { Search2, Addfollow, Follow, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import TabBar from '@/components/TabBar.vue'
import { Search2, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { useFavorite } from '@/composables/useFavorite'
import './index.less'
// 响应式数据
......@@ -134,7 +134,7 @@ const searchValue = ref('')
const onBlurSearch = () => {
console.warn(searchValue.value)
}
const favoriteIds = ref(['2', '4', '6'])
// 收藏功能现在使用基于对象属性的模式
// Filter states - 使用NutUI Menu组件
const selectedBrand = ref('全部品牌')
......@@ -255,24 +255,8 @@ const scrollStyle = computed(() => {
* 切换收藏状态
* @param {string} carId - 车辆ID
*/
const toggleFavorite = (carId) => {
const index = favoriteIds.value.indexOf(carId)
if (index > -1) {
favoriteIds.value.splice(index, 1)
Taro.showToast({
title: '取消收藏',
icon: 'none',
duration: 2000
})
} else {
favoriteIds.value.push(carId)
Taro.showToast({
title: '收藏成功',
icon: 'success',
duration: 2000
})
}
}
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
/**
* 点击车辆卡片
......
<!--
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-10 20:17:31
* @LastEditTime: 2025-07-10 21:30:57
* @FilePath: /jgdl/src/pages/index/index.vue
* @Description: 捡个电驴首页
-->
......@@ -169,10 +169,11 @@ import SearchPopup from '@/components/SearchPopup.vue'
import "./index.less";
// 导入接口
import { getRecommendVehicleAPI, getVehicleListAPI } from '@/api/car';
import { useFavorite } from '@/composables/useFavorite'
// 响应式数据
const searchValue = ref('')
const favoriteIds = ref([])
// favoriteIds 已移除,现在使用基于对象属性的收藏模式
const showSearchPopup = ref(false)
const onSearchHandle = () => {
......@@ -189,26 +190,8 @@ const featuredScooters = ref([])
// 最新上架数据
const latestScooters = ref([])
/**
* 切换收藏状态
* @param {string} scooterId - 电动车ID
*/
const toggleFavorite = async (scooter) => {
scooter.is_favorite = !scooter.is_favorite
if (scooter.is_favorite) {
Taro.showToast({
title: '收藏成功',
icon: 'success',
duration: 2000
})
} else {
Taro.showToast({
title: '取消收藏',
icon: 'none',
duration: 2000
})
}
}
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
const onMoreRecommendClick = () => {
Taro.navigateTo({
......
......@@ -61,8 +61,8 @@
</view>
</view>
<view class="flex-1 p-3 relative">
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)">
<Heart1 v-if="!favoriteIds.includes(car.id)" size="22" color="#9ca3af" />
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car)">
<Heart1 v-if="!car.is_favorite" size="22" color="#9ca3af" />
<HeartFill v-else size="22" color="#ef4444" />
</view>
<text class="font-medium text-sm block">{{ car.name }}</text>
......@@ -115,10 +115,10 @@
import Taro from '@tarojs/taro'
import { ref, computed, onMounted } from 'vue'
import { Search2, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import TabBar from '@/components/TabBar.vue'
// TabBar 组件已移除
import { useFavorite } from '@/composables/useFavorite'
import './index.less'
// 导入接口
import { getVehicleListAPI } from '@/api/car';
// 接口导入已移除,现在使用模拟数据;
import { getVehicleBrandsAPI, getSchoolsAPI } from '@/api/other';
// 响应式数据
......@@ -126,7 +126,7 @@ const searchValue = ref('')
const onBlurSearch = () => {
console.warn(searchValue.value)
}
const favoriteIds = ref(['2', '4', '6'])
// 收藏功能现在使用基于对象属性的模式
// Filter states - 使用NutUI Menu组件
const selectedBrand = ref('')
......@@ -218,24 +218,8 @@ const scrollStyle = computed(() => {
* 切换收藏状态
* @param {string} carId - 车辆ID
*/
const toggleFavorite = (carId) => {
const index = favoriteIds.value.indexOf(carId)
if (index > -1) {
favoriteIds.value.splice(index, 1)
Taro.showToast({
title: '取消收藏',
icon: 'none',
duration: 2000
})
} else {
favoriteIds.value.push(carId)
Taro.showToast({
title: '收藏成功',
icon: 'success',
duration: 2000
})
}
}
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
/**
* 点击车辆卡片
......
......@@ -43,8 +43,8 @@
</view>
</view>
<view class="flex-1 p-3 relative">
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(scooter.id)">
<Heart1 v-if="!favoriteIds.includes(scooter.id)" size="22" color="#9ca3af" />
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(scooter)">
<Heart1 v-if="!scooter.is_favorite" size="22" color="#9ca3af" />
<HeartFill v-else size="22" color="#ef4444" />
</view>
<text class="font-medium text-sm block">{{ scooter.name }}</text>
......@@ -97,8 +97,8 @@
<view class="relative p-2">
<image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill"
class="w-full h-36 object-cover rounded-lg" />
<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)">
<Heart1 v-if="!favoriteIds.includes(scooter.id)" size="20" color="#9ca3af" />
<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)">
<Heart1 v-if="!scooter.is_favorite" size="20" color="#9ca3af" />
<HeartFill v-else size="20" color="#ef4444" />
</view>
<view v-if="scooter.isVerified"
......@@ -136,13 +136,14 @@ import { ref } from 'vue'
import Taro from '@tarojs/taro'
import { Search2, RectRight, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import TabBar from '@/components/TabBar.vue'
import { useFavorite } from '@/composables/useFavorite'
// 响应式数据
const searchValue = ref('')
const onBlurSearch = () => {
console.warn(searchValue.value)
}
const favoriteIds = ref(['5', '7', '1'])
// 收藏功能现在使用基于对象属性的模式
// 无限滚动相关状态
const loading = ref(false)
......@@ -282,24 +283,8 @@ const featuredScooters = ref([
* 切换收藏状态
* @param {string} scooterId - 电动车ID
*/
const toggleFavorite = (scooterId) => {
const index = favoriteIds.value.indexOf(scooterId)
if (index > -1) {
favoriteIds.value.splice(index, 1)
Taro.showToast({
title: '取消收藏',
icon: 'none',
duration: 2000
})
} else {
favoriteIds.value.push(scooterId)
Taro.showToast({
title: '收藏成功',
icon: 'success',
duration: 2000
})
}
}
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
/**
* 点击产品卡片
......
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-09 10:42:01
* @LastEditTime: 2025-07-10 22:03:48
* @FilePath: /jgdl/src/pages/productDetail/index.vue
* @Description: 商品详情页
-->
......@@ -30,9 +30,9 @@
</view>
</button>
</view>
<view @tap="toggleFavorite"
<view @tap="toggleFavorite(product)"
class="w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center">
<HeartFill v-if="isFavorite" size="18" color="#ef4444" />
<HeartFill v-if="product.is_favorite" size="18" color="#ef4444" />
<Heart1 v-else size="18" color="#666" />
</view>
</view>
......@@ -264,6 +264,7 @@ import { ref } from 'vue'
import Taro, { useShareAppMessage } from '@tarojs/taro'
import { Share, Heart1, HeartFill, Message } from '@nutui/icons-vue-taro'
import payCard from '@/components/payCard.vue'
import { useFavorite } from '@/composables/useFavorite'
import avatarImg from '@/assets/images/avatar.png'
import { getCurrentPageParam } from "@/utils/weapp"
import { checkPermission, PERMISSION_TYPES } from '@/utils/permission'
......@@ -279,7 +280,7 @@ wx.showShareMenu({
// 响应式数据
const currentImageIndex = ref(0)
const isFavorite = ref(false)
// isFavorite 功能现在由 useFavorite composable 提供
const showWechat = ref(false)
const showContactModal = ref(false)
const show_pay = ref(false)
......@@ -353,7 +354,8 @@ const product = ref({
avatar: 'https://randomuser.me/api/portraits/men/32.jpg',
wechat: 'li_student_2023',
phone: '138****8888'
}
},
is_favorite: false
})
/**
......@@ -364,16 +366,8 @@ const onSwiperChange = (index) => {
currentImageIndex.value = index
}
/**
* 切换收藏状态
*/
const toggleFavorite = () => {
isFavorite.value = !isFavorite.value
Taro.showToast({
title: isFavorite.value ? '已收藏' : '已取消收藏',
icon: 'none'
})
}
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
/**
* 显示微信号弹框
......
......@@ -61,8 +61,8 @@
</view>
</view>
<view class="flex-1 p-3 relative">
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car.id)">
<Heart1 v-if="!favoriteIds.includes(car.id)" size="22" color="#9ca3af" />
<view class="absolute top-3 right-4" @tap.stop="() => toggleFavorite(car)">
<Heart1 v-if="!car.is_favorite" size="22" color="#9ca3af" />
<HeartFill v-else size="22" color="#ef4444" />
</view>
<text class="font-medium text-sm block">{{ car.name }}</text>
......@@ -115,6 +115,7 @@
import Taro from '@tarojs/taro'
import { ref, computed, onMounted } from 'vue'
import { Search2, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { useFavorite } from '@/composables/useFavorite'
import './index.less'
// 响应式数据
......@@ -122,7 +123,7 @@ const searchValue = ref('')
const onBlurSearch = () => {
console.warn(searchValue.value)
}
const favoriteIds = ref(['2', '4', '6'])
// 收藏功能现在使用基于对象属性的模式
// Filter states - 使用NutUI Menu组件
const selectedBrand = ref('全部品牌')
......@@ -235,24 +236,8 @@ const scrollStyle = computed(() => {
* 切换收藏状态
* @param {string} carId - 车辆ID
*/
const toggleFavorite = (carId) => {
const index = favoriteIds.value.indexOf(carId)
if (index > -1) {
favoriteIds.value.splice(index, 1)
Taro.showToast({
title: '取消收藏',
icon: 'none',
duration: 2000
})
} else {
favoriteIds.value.push(carId)
Taro.showToast({
title: '收藏成功',
icon: 'success',
duration: 2000
})
}
}
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
/**
* 点击车辆卡片
......
<!--
* @Date: 2022-09-19 14:11:06
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-04 16:57:31
* @LastEditTime: 2025-07-10 21:38:42
* @FilePath: /jgdl/src/pages/search/index.vue
* @Description: 搜索页面
-->
......@@ -58,8 +58,8 @@
<view class="relative p-2">
<image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill"
class="w-full h-36 object-cover rounded-lg" />
<view class="absolute top-4 right-3 w-7 h-7 rounded-full bg-white bg-opacity-90" @tap.stop="() => toggleFavorite(scooter.id)" style="padding-top: 12rpx; padding-left: 10rpx;">
<Heart1 v-if="!favoriteIds.includes(scooter.id)" size="22" :color="'#9ca3af'" />
<view class="absolute top-4 right-3 w-7 h-7 rounded-full bg-white bg-opacity-90" @tap.stop="() => toggleFavorite(scooter)" style="padding-top: 12rpx; padding-left: 10rpx;">
<Heart1 v-if="!scooter.is_favorite" size="22" :color="'#9ca3af'" />
<HeartFill v-else size="22" :color="'#ef4444'" />
</view>
<view v-if="scooter.isVerified"
......@@ -123,11 +123,12 @@
import { ref, computed, onMounted } from 'vue'
import Taro from '@tarojs/taro'
import { Search2, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { useFavorite } from '@/composables/useFavorite'
import "./index.less";
// 响应式数据
const searchValue = ref('')
const favoriteIds = ref(['5', '7', '1'])
// 收藏功能现在使用基于对象属性的模式
const hasSearched = ref(false)
// 滚动相关
......@@ -335,24 +336,8 @@ const loadMore = () => {
* 切换收藏状态
* @param {string} id 商品ID
*/
const toggleFavorite = (id) => {
const index = favoriteIds.value.indexOf(id)
if (index > -1) {
favoriteIds.value.splice(index, 1)
Taro.showToast({
title: '取消收藏',
icon: 'none',
duration: 2000
})
} else {
favoriteIds.value.push(id)
Taro.showToast({
title: '收藏成功',
icon: 'success',
duration: 2000
})
}
}
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
/**
* 商品点击事件
......