hookehuyr

feat(组件): 新增精品推荐组件并替换多处重复代码

将精品推荐功能抽离为独立组件FeaturedRecommendations,并在首页和发布页中使用
移除重复的精品推荐代码逻辑,统一数据获取和展示方式
......@@ -8,6 +8,7 @@ export {}
declare module 'vue' {
export interface GlobalComponents {
BrandModelPicker: typeof import('./src/components/BrandModelPicker.vue')['default']
FeaturedRecommendations: typeof import('./src/components/FeaturedRecommendations.vue')['default']
NavBar: typeof import('./src/components/navBar.vue')['default']
NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet']
NutButton: typeof import('@nutui/nutui-taro')['Button']
......
<template>
<view class="px-4 mt-4">
<view class="flex justify-between items-center mb-2">
<text class="text-lg font-medium">精品推荐</text>
<view class="text-sm text-gray-500 flex items-center" @tap="onMoreRecommendClick">
<text>更多</text>
<RectRight size="12" />
</view>
</view>
<view class="grid grid-cols-2 gap-3">
<view v-for="scooter in featuredScooters" :key="scooter.id"
class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)">
<view class="relative p-2">
<image :src="scooter.front_photo" 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)">
<Heart1 v-if="!scooter.is_favorite" size="22" :color="'#9ca3af'" />
<HeartFill v-else size="22" :color="'#ef4444'" />
</view>
<view v-if="scooter.verification_status === 5"
class="absolute bottom-4 right-4 text-white text-xs px-1.5 py-0.5 rounded flex items-center"
style="background-color: #EB5305;">
<Check size="12" color="#ffffff" class="mr-0.5" />
<text class="text-white">认证</text>
</view>
</view>
<view class="p-2 pl-3">
<text class="font-medium text-sm block">{{ scooter.brand }} {{ scooter.model }}</text>
<text class="text-xs text-gray-500 block mt-1 mb-1">
{{ scooter.manufacture_year }}年 · {{ scooter.school_name }}
</text>
<view class="mt-1">
<text class="text-orange-500 font-bold" style="font-size: 1.25rem;">
¥{{ scooter.price.toLocaleString() }}
</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script setup>
import Taro from '@tarojs/taro'
import { ref, onMounted } from 'vue'
import { RectRight, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { getRecommendVehicleAPI } from '@/api/car'
import { useFavorite } from '@/composables/useFavorite'
import { DEFAULT_COVER_IMG } from '@/utils/config'
// 定义组件名称
defineOptions({
name: 'FeaturedRecommendations'
})
// 精品推荐数据
const featuredScooters = ref([])
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
/**
* 查看更多点击事件
*/
const onMoreRecommendClick = () => {
Taro.navigateTo({
url: '/pages/recommendCarList/index'
})
}
/**
* 点击产品卡片
* @param {Object} scooter - 电动车信息
*/
const onProductClick = (scooter) => {
Taro.navigateTo({
url: `/pages/productDetail/index?id=${scooter.id}`
})
}
/**
* 加载精品推荐数据
*/
const loadFeaturedData = async () => {
try {
const res = await getRecommendVehicleAPI({ section: 3, page: 0, limit: 4 })
if (res.code) {
// 处理图片数据
const processedData = res.data.list.map(item => ({
...item,
front_photo: item.front_photo || DEFAULT_COVER_IMG,
// 确保价格为数字类型
price: Number(item.price) || 0,
market_price: Number(item.market_price) || 0
}))
featuredScooters.value = processedData
}
} catch (error) {
console.error('加载精品推荐数据失败:', error)
}
}
// 组件挂载时加载数据
onMounted(() => {
loadFeaturedData()
})
</script>
<style lang="less" scoped>
// 使用Tailwind CSS类,只保留必要的自定义样式
.grid {
display: grid;
}
.grid-cols-2 {
grid-template-columns: repeat(2, 1fr);
}
.gap-3 {
gap: 0.75rem;
}
// 确保图片正确显示
image {
display: block;
}
</style>
\ No newline at end of file
<!--
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-11 10:58:14
* @LastEditTime: 2025-07-11 11:14:57
* @FilePath: /jgdl/src/pages/index/index.vue
* @Description: 捡个电驴首页
-->
......@@ -65,52 +65,7 @@
</view>
<!-- Featured Recommendations -->
<view class="px-4 mt-4">
<view class="flex justify-between items-center mb-2">
<text class="text-lg font-medium">精品推荐</text>
<view class="text-sm text-gray-500 flex items-center" @tap="onMoreRecommendClick">
<text>更多</text>
<RectRight size="12" />
</view>
</view>
<view class="grid grid-cols-2 gap-3">
<view v-for="scooter in featuredScooters" :key="scooter.id"
class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)">
<view class="relative p-2">
<image :src="scooter.front_photo" 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)">
<Heart1 v-if="!scooter.is_favorite" size="22" :color="'#9ca3af'" />
<HeartFill v-else size="22" :color="'#ef4444'" />
</view>
<view v-if="scooter.verification_status === 5"
class="absolute bottom-4 right-4 text-white text-xs px-1.5 py-0.5 rounded flex items-center"
style="background-color: #EB5305;">
<Check size="12" color="#ffffff" class="mr-0.5" />
<text class="text-white">认证</text>
</view>
</view>
<view class="p-2 pl-3">
<text class="font-medium text-sm block">{{ scooter.brand }} {{ scooter.model }}</text>
<text class="text-xs text-gray-500 block mt-1 mb-1">
{{ scooter.manufacture_year }}年 · {{ scooter.school_name }}
<!-- <text v-if="scooter.battery_capacity_ah">电池容量{{ scooter.battery_capacity_ah }}Ah</text>
<text v-if="scooter.total_mileage_km"> 行驶{{ scooter.total_mileage_km }}公里</text> -->
</text>
<view class="mt-1">
<text class="text-orange-500 font-bold" style="font-size: 1.25rem;">
¥{{ scooter.price.toLocaleString() }}
</text>
<!-- <text v-if="scooter.isVerified" class="ml-2 text-xs px-1 py-0.5 bg-orange-100 text-orange-500 rounded">
低于市场价10%
</text> -->
<!-- <text class="text-xs text-gray-500 mt-1 block">{{ scooter.school }}</text> -->
</view>
</view>
</view>
</view>
</view>
<FeaturedRecommendations />
<!-- Latest Listings -->
<view class="px-4 mt-6 mb-20">
......@@ -173,6 +128,7 @@ import { ref, onMounted } from 'vue'
import { Clock, Star, RectRight, Check, Search2, Shop, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import TabBar from '@/components/TabBar.vue'
import SearchPopup from '@/components/SearchPopup.vue'
import FeaturedRecommendations from '@/components/FeaturedRecommendations.vue'
import "./index.less";
// 导入接口
import { getRecommendVehicleAPI, getVehicleListAPI } from '@/api/car';
......@@ -191,21 +147,12 @@ const onSearchHandle = () => {
// Banner图片
const bannerImages = ref([])
// 精品推荐数据
const featuredScooters = ref([])
// 最新上架数据
const latestScooters = ref([])
// 使用收藏功能composables
const { toggleFavorite } = useFavorite()
const onMoreRecommendClick = () => {
Taro.navigateTo({
url: '/pages/recommendCarList/index'
})
}
/**
* 点击产品卡片
* @param {Object} scooter - 电动车信息
......@@ -292,21 +239,6 @@ onMounted(async () => {
if (res1.code) {
bannerImages.value = res1.data.list.map(item => item.front_photo);
}
// 获取精品推荐
const res2 = await getRecommendVehicleAPI({ section: 3 })
if (res2.code) {
featuredScooters.value = res2.data.list
// 处理图片数据
const processedData = res2.data.list.map(item => ({
...item,
front_photo: item.front_photo || DEFAULT_COVER_IMG,
// 确保价格为数字类型
price: Number(item.price) || 0,
market_price: Number(item.market_price) || 0
}))
featuredScooters.value = processedData
}
// 获取最新上架
const res3 = await getVehicleListAPI({ page: 0, limit: 5 })
if (res3.code) {
......
......@@ -83,46 +83,8 @@
</view>
<!-- Featured recommendations section -->
<view class="mt-6 mb-20 ml-4 mr-4">
<view class="flex justify-between items-center mb-3">
<text class="text-lg font-medium">精品推荐</text>
<view class="text-sm text-gray-500 flex items-center" @tap="onMoreRecommendClick">
<text>更多</text>
<RectRight size="12" />
</view>
</view>
<view class="grid grid-cols-2 gap-3">
<view v-for="scooter in featuredScooters" :key="scooter.id"
class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)">
<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)">
<Heart1 v-if="!scooter.is_favorite" size="20" color="#9ca3af" />
<HeartFill v-else size="20" color="#ef4444" />
</view>
<view v-if="scooter.isVerified"
class="absolute bottom-4 right-4 bg-orange-500 text-white text-xs px-1.5 py-0.5 rounded flex items-center">
<Check size="12" color="#ffffff" class="mr-0.5" />
<text class="text-white">认证</text>
</view>
</view>
<view class="p-2">
<text class="font-medium text-sm block">{{ scooter.name }}</text>
<text class="text-xs text-gray-500 block">
{{ scooter.year }} ·
<text v-if="scooter.batteryHealth">电池健康度{{ scooter.batteryHealth }}%</text>
<text v-if="scooter.mileage"> 行驶{{ scooter.mileage }}公里</text>
</text>
<view class="mt-1">
<text class="text-orange-500 font-bold">
¥{{ scooter.price.toLocaleString() }}
</text>
<text class="text-xs text-gray-500 mt-1 block">{{ scooter.school }}</text>
</view>
</view>
</view>
</view>
<view class="mb-20">
<FeaturedRecommendations />
</view>
</view>
......@@ -134,8 +96,9 @@
<script setup>
import { ref } from 'vue'
import Taro from '@tarojs/taro'
import { Search2, RectRight, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import { Search2, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
import TabBar from '@/components/TabBar.vue'
import FeaturedRecommendations from '@/components/FeaturedRecommendations.vue'
import { useFavorite } from '@/composables/useFavorite'
// 响应式数据
......@@ -234,50 +197,7 @@ const scooters = ref([
}
])
// 精品推荐数据 - 参考PostPage.tsx
const featuredScooters = ref([
{
id: '1',
name: '小龟电动车',
year: '2023年',
school: '上海理工大学',
price: 3880,
batteryHealth: 92,
mileage: 2000,
imageUrl: 'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
isVerified: true
},
{
id: '2',
name: '立马电动车',
year: '2022年',
school: '上海复旦大学',
price: 2999,
batteryHealth: 95,
mileage: 1500,
imageUrl: 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
},
{
id: '3',
name: '绿源电动车',
year: '2024年',
school: '上海同济大学',
price: 6000,
batteryHealth: 98,
mileage: 500,
imageUrl: 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
},
{
id: '4',
name: '新日电动车',
year: '2024年',
school: '上海同济大学',
price: 6700,
batteryHealth: 96,
mileage: 500,
imageUrl: 'https://images.unsplash.com/photo-1595941069915-4ebc5197c14a?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
}
])
/**
* 切换收藏状态
......@@ -296,14 +216,7 @@ const onProductClick = (scooter) => {
})
}
/**
* 查看更多点击事件
*/
const onMoreRecommendClick = () => {
Taro.navigateTo({
url: '/pages/recommendCarList/index'
})
}
// Menu组件事件处理方法
/**
......