hookehuyr

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

将精品推荐功能抽离为独立组件FeaturedRecommendations,并在首页和发布页中使用
移除重复的精品推荐代码逻辑,统一数据获取和展示方式
...@@ -8,6 +8,7 @@ export {} ...@@ -8,6 +8,7 @@ export {}
8 declare module 'vue' { 8 declare module 'vue' {
9 export interface GlobalComponents { 9 export interface GlobalComponents {
10 BrandModelPicker: typeof import('./src/components/BrandModelPicker.vue')['default'] 10 BrandModelPicker: typeof import('./src/components/BrandModelPicker.vue')['default']
11 + FeaturedRecommendations: typeof import('./src/components/FeaturedRecommendations.vue')['default']
11 NavBar: typeof import('./src/components/navBar.vue')['default'] 12 NavBar: typeof import('./src/components/navBar.vue')['default']
12 NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet'] 13 NutActionSheet: typeof import('@nutui/nutui-taro')['ActionSheet']
13 NutButton: typeof import('@nutui/nutui-taro')['Button'] 14 NutButton: typeof import('@nutui/nutui-taro')['Button']
......
1 +<template>
2 + <view class="px-4 mt-4">
3 + <view class="flex justify-between items-center mb-2">
4 + <text class="text-lg font-medium">精品推荐</text>
5 + <view class="text-sm text-gray-500 flex items-center" @tap="onMoreRecommendClick">
6 + <text>更多</text>
7 + <RectRight size="12" />
8 + </view>
9 + </view>
10 + <view class="grid grid-cols-2 gap-3">
11 + <view v-for="scooter in featuredScooters" :key="scooter.id"
12 + class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)">
13 + <view class="relative p-2">
14 + <image :src="scooter.front_photo" mode="aspectFill" class="w-full h-36 object-cover rounded-lg" />
15 + <view
16 + class="absolute top-4 right-3 w-7 h-7 rounded-full bg-white bg-opacity-80 flex items-center justify-center"
17 + @tap.stop="() => toggleFavorite(scooter)">
18 + <Heart1 v-if="!scooter.is_favorite" size="22" :color="'#9ca3af'" />
19 + <HeartFill v-else size="22" :color="'#ef4444'" />
20 + </view>
21 + <view v-if="scooter.verification_status === 5"
22 + class="absolute bottom-4 right-4 text-white text-xs px-1.5 py-0.5 rounded flex items-center"
23 + style="background-color: #EB5305;">
24 + <Check size="12" color="#ffffff" class="mr-0.5" />
25 + <text class="text-white">认证</text>
26 + </view>
27 + </view>
28 + <view class="p-2 pl-3">
29 + <text class="font-medium text-sm block">{{ scooter.brand }} {{ scooter.model }}</text>
30 + <text class="text-xs text-gray-500 block mt-1 mb-1">
31 + {{ scooter.manufacture_year }}年 · {{ scooter.school_name }}
32 + </text>
33 + <view class="mt-1">
34 + <text class="text-orange-500 font-bold" style="font-size: 1.25rem;">
35 + ¥{{ scooter.price.toLocaleString() }}
36 + </text>
37 + </view>
38 + </view>
39 + </view>
40 + </view>
41 + </view>
42 +</template>
43 +
44 +<script setup>
45 +import Taro from '@tarojs/taro'
46 +import { ref, onMounted } from 'vue'
47 +import { RectRight, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
48 +import { getRecommendVehicleAPI } from '@/api/car'
49 +import { useFavorite } from '@/composables/useFavorite'
50 +import { DEFAULT_COVER_IMG } from '@/utils/config'
51 +
52 +// 定义组件名称
53 +defineOptions({
54 + name: 'FeaturedRecommendations'
55 +})
56 +
57 +// 精品推荐数据
58 +const featuredScooters = ref([])
59 +
60 +// 使用收藏功能composables
61 +const { toggleFavorite } = useFavorite()
62 +
63 +/**
64 + * 查看更多点击事件
65 + */
66 +const onMoreRecommendClick = () => {
67 + Taro.navigateTo({
68 + url: '/pages/recommendCarList/index'
69 + })
70 +}
71 +
72 +/**
73 + * 点击产品卡片
74 + * @param {Object} scooter - 电动车信息
75 + */
76 +const onProductClick = (scooter) => {
77 + Taro.navigateTo({
78 + url: `/pages/productDetail/index?id=${scooter.id}`
79 + })
80 +}
81 +
82 +/**
83 + * 加载精品推荐数据
84 + */
85 +const loadFeaturedData = async () => {
86 + try {
87 + const res = await getRecommendVehicleAPI({ section: 3, page: 0, limit: 4 })
88 + if (res.code) {
89 + // 处理图片数据
90 + const processedData = res.data.list.map(item => ({
91 + ...item,
92 + front_photo: item.front_photo || DEFAULT_COVER_IMG,
93 + // 确保价格为数字类型
94 + price: Number(item.price) || 0,
95 + market_price: Number(item.market_price) || 0
96 + }))
97 + featuredScooters.value = processedData
98 + }
99 + } catch (error) {
100 + console.error('加载精品推荐数据失败:', error)
101 + }
102 +}
103 +
104 +// 组件挂载时加载数据
105 +onMounted(() => {
106 + loadFeaturedData()
107 +})
108 +</script>
109 +
110 +<style lang="less" scoped>
111 +// 使用Tailwind CSS类,只保留必要的自定义样式
112 +.grid {
113 + display: grid;
114 +}
115 +
116 +.grid-cols-2 {
117 + grid-template-columns: repeat(2, 1fr);
118 +}
119 +
120 +.gap-3 {
121 + gap: 0.75rem;
122 +}
123 +
124 +// 确保图片正确显示
125 +image {
126 + display: block;
127 +}
128 +</style>
...\ No newline at end of file ...\ No newline at end of file
1 <!-- 1 <!--
2 * @Date: 2025-06-28 10:33:00 2 * @Date: 2025-06-28 10:33:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-11 10:58:14 4 + * @LastEditTime: 2025-07-11 11:14:57
5 * @FilePath: /jgdl/src/pages/index/index.vue 5 * @FilePath: /jgdl/src/pages/index/index.vue
6 * @Description: 捡个电驴首页 6 * @Description: 捡个电驴首页
7 --> 7 -->
...@@ -65,52 +65,7 @@ ...@@ -65,52 +65,7 @@
65 </view> 65 </view>
66 66
67 <!-- Featured Recommendations --> 67 <!-- Featured Recommendations -->
68 - <view class="px-4 mt-4"> 68 + <FeaturedRecommendations />
69 - <view class="flex justify-between items-center mb-2">
70 - <text class="text-lg font-medium">精品推荐</text>
71 - <view class="text-sm text-gray-500 flex items-center" @tap="onMoreRecommendClick">
72 - <text>更多</text>
73 - <RectRight size="12" />
74 - </view>
75 - </view>
76 - <view class="grid grid-cols-2 gap-3">
77 - <view v-for="scooter in featuredScooters" :key="scooter.id"
78 - class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)">
79 - <view class="relative p-2">
80 - <image :src="scooter.front_photo" mode="aspectFill" class="w-full h-36 object-cover rounded-lg" />
81 - <view
82 - class="absolute top-4 right-3 w-7 h-7 rounded-full bg-white bg-opacity-80 flex items-center justify-center"
83 - @tap.stop="() => toggleFavorite(scooter)">
84 - <Heart1 v-if="!scooter.is_favorite" size="22" :color="'#9ca3af'" />
85 - <HeartFill v-else size="22" :color="'#ef4444'" />
86 - </view>
87 - <view v-if="scooter.verification_status === 5"
88 - class="absolute bottom-4 right-4 text-white text-xs px-1.5 py-0.5 rounded flex items-center"
89 - style="background-color: #EB5305;">
90 - <Check size="12" color="#ffffff" class="mr-0.5" />
91 - <text class="text-white">认证</text>
92 - </view>
93 - </view>
94 - <view class="p-2 pl-3">
95 - <text class="font-medium text-sm block">{{ scooter.brand }} {{ scooter.model }}</text>
96 - <text class="text-xs text-gray-500 block mt-1 mb-1">
97 - {{ scooter.manufacture_year }}年 · {{ scooter.school_name }}
98 - <!-- <text v-if="scooter.battery_capacity_ah">电池容量{{ scooter.battery_capacity_ah }}Ah</text>
99 - <text v-if="scooter.total_mileage_km"> 行驶{{ scooter.total_mileage_km }}公里</text> -->
100 - </text>
101 - <view class="mt-1">
102 - <text class="text-orange-500 font-bold" style="font-size: 1.25rem;">
103 - ¥{{ scooter.price.toLocaleString() }}
104 - </text>
105 - <!-- <text v-if="scooter.isVerified" class="ml-2 text-xs px-1 py-0.5 bg-orange-100 text-orange-500 rounded">
106 - 低于市场价10%
107 - </text> -->
108 - <!-- <text class="text-xs text-gray-500 mt-1 block">{{ scooter.school }}</text> -->
109 - </view>
110 - </view>
111 - </view>
112 - </view>
113 - </view>
114 69
115 <!-- Latest Listings --> 70 <!-- Latest Listings -->
116 <view class="px-4 mt-6 mb-20"> 71 <view class="px-4 mt-6 mb-20">
...@@ -173,6 +128,7 @@ import { ref, onMounted } from 'vue' ...@@ -173,6 +128,7 @@ import { ref, onMounted } from 'vue'
173 import { Clock, Star, RectRight, Check, Search2, Shop, Heart1, HeartFill } from '@nutui/icons-vue-taro' 128 import { Clock, Star, RectRight, Check, Search2, Shop, Heart1, HeartFill } from '@nutui/icons-vue-taro'
174 import TabBar from '@/components/TabBar.vue' 129 import TabBar from '@/components/TabBar.vue'
175 import SearchPopup from '@/components/SearchPopup.vue' 130 import SearchPopup from '@/components/SearchPopup.vue'
131 +import FeaturedRecommendations from '@/components/FeaturedRecommendations.vue'
176 import "./index.less"; 132 import "./index.less";
177 // 导入接口 133 // 导入接口
178 import { getRecommendVehicleAPI, getVehicleListAPI } from '@/api/car'; 134 import { getRecommendVehicleAPI, getVehicleListAPI } from '@/api/car';
...@@ -191,21 +147,12 @@ const onSearchHandle = () => { ...@@ -191,21 +147,12 @@ const onSearchHandle = () => {
191 // Banner图片 147 // Banner图片
192 const bannerImages = ref([]) 148 const bannerImages = ref([])
193 149
194 -// 精品推荐数据
195 -const featuredScooters = ref([])
196 -
197 // 最新上架数据 150 // 最新上架数据
198 const latestScooters = ref([]) 151 const latestScooters = ref([])
199 152
200 // 使用收藏功能composables 153 // 使用收藏功能composables
201 const { toggleFavorite } = useFavorite() 154 const { toggleFavorite } = useFavorite()
202 155
203 -const onMoreRecommendClick = () => {
204 - Taro.navigateTo({
205 - url: '/pages/recommendCarList/index'
206 - })
207 -}
208 -
209 /** 156 /**
210 * 点击产品卡片 157 * 点击产品卡片
211 * @param {Object} scooter - 电动车信息 158 * @param {Object} scooter - 电动车信息
...@@ -292,21 +239,6 @@ onMounted(async () => { ...@@ -292,21 +239,6 @@ onMounted(async () => {
292 if (res1.code) { 239 if (res1.code) {
293 bannerImages.value = res1.data.list.map(item => item.front_photo); 240 bannerImages.value = res1.data.list.map(item => item.front_photo);
294 } 241 }
295 - // 获取精品推荐
296 - const res2 = await getRecommendVehicleAPI({ section: 3 })
297 - if (res2.code) {
298 - featuredScooters.value = res2.data.list
299 - // 处理图片数据
300 - const processedData = res2.data.list.map(item => ({
301 - ...item,
302 - front_photo: item.front_photo || DEFAULT_COVER_IMG,
303 - // 确保价格为数字类型
304 - price: Number(item.price) || 0,
305 - market_price: Number(item.market_price) || 0
306 - }))
307 -
308 - featuredScooters.value = processedData
309 - }
310 // 获取最新上架 242 // 获取最新上架
311 const res3 = await getVehicleListAPI({ page: 0, limit: 5 }) 243 const res3 = await getVehicleListAPI({ page: 0, limit: 5 })
312 if (res3.code) { 244 if (res3.code) {
......
...@@ -83,46 +83,8 @@ ...@@ -83,46 +83,8 @@
83 </view> 83 </view>
84 84
85 <!-- Featured recommendations section --> 85 <!-- Featured recommendations section -->
86 - <view class="mt-6 mb-20 ml-4 mr-4"> 86 + <view class="mb-20">
87 - <view class="flex justify-between items-center mb-3"> 87 + <FeaturedRecommendations />
88 - <text class="text-lg font-medium">精品推荐</text>
89 - <view class="text-sm text-gray-500 flex items-center" @tap="onMoreRecommendClick">
90 - <text>更多</text>
91 - <RectRight size="12" />
92 - </view>
93 - </view>
94 - <view class="grid grid-cols-2 gap-3">
95 - <view v-for="scooter in featuredScooters" :key="scooter.id"
96 - class="bg-white rounded-lg shadow-sm overflow-hidden" @tap="() => onProductClick(scooter)">
97 - <view class="relative p-2">
98 - <image :src="scooter.imageUrl" :alt="scooter.name" mode="aspectFill"
99 - class="w-full h-36 object-cover rounded-lg" />
100 - <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)">
101 - <Heart1 v-if="!scooter.is_favorite" size="20" color="#9ca3af" />
102 - <HeartFill v-else size="20" color="#ef4444" />
103 - </view>
104 - <view v-if="scooter.isVerified"
105 - class="absolute bottom-4 right-4 bg-orange-500 text-white text-xs px-1.5 py-0.5 rounded flex items-center">
106 - <Check size="12" color="#ffffff" class="mr-0.5" />
107 - <text class="text-white">认证</text>
108 - </view>
109 - </view>
110 - <view class="p-2">
111 - <text class="font-medium text-sm block">{{ scooter.name }}</text>
112 - <text class="text-xs text-gray-500 block">
113 - {{ scooter.year }} ·
114 - <text v-if="scooter.batteryHealth">电池健康度{{ scooter.batteryHealth }}%</text>
115 - <text v-if="scooter.mileage"> 行驶{{ scooter.mileage }}公里</text>
116 - </text>
117 - <view class="mt-1">
118 - <text class="text-orange-500 font-bold">
119 - ¥{{ scooter.price.toLocaleString() }}
120 - </text>
121 - <text class="text-xs text-gray-500 mt-1 block">{{ scooter.school }}</text>
122 - </view>
123 - </view>
124 - </view>
125 - </view>
126 </view> 88 </view>
127 </view> 89 </view>
128 90
...@@ -134,8 +96,9 @@ ...@@ -134,8 +96,9 @@
134 <script setup> 96 <script setup>
135 import { ref } from 'vue' 97 import { ref } from 'vue'
136 import Taro from '@tarojs/taro' 98 import Taro from '@tarojs/taro'
137 -import { Search2, RectRight, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro' 99 +import { Search2, Check, Heart1, HeartFill } from '@nutui/icons-vue-taro'
138 import TabBar from '@/components/TabBar.vue' 100 import TabBar from '@/components/TabBar.vue'
101 +import FeaturedRecommendations from '@/components/FeaturedRecommendations.vue'
139 import { useFavorite } from '@/composables/useFavorite' 102 import { useFavorite } from '@/composables/useFavorite'
140 103
141 // 响应式数据 104 // 响应式数据
...@@ -234,50 +197,7 @@ const scooters = ref([ ...@@ -234,50 +197,7 @@ const scooters = ref([
234 } 197 }
235 ]) 198 ])
236 199
237 -// 精品推荐数据 - 参考PostPage.tsx 200 +
238 -const featuredScooters = ref([
239 - {
240 - id: '1',
241 - name: '小龟电动车',
242 - year: '2023年',
243 - school: '上海理工大学',
244 - price: 3880,
245 - batteryHealth: 92,
246 - mileage: 2000,
247 - imageUrl: 'https://images.unsplash.com/photo-1558981285-6f0c94958bb6?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60',
248 - isVerified: true
249 - },
250 - {
251 - id: '2',
252 - name: '立马电动车',
253 - year: '2022年',
254 - school: '上海复旦大学',
255 - price: 2999,
256 - batteryHealth: 95,
257 - mileage: 1500,
258 - imageUrl: 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
259 - },
260 - {
261 - id: '3',
262 - name: '绿源电动车',
263 - year: '2024年',
264 - school: '上海同济大学',
265 - price: 6000,
266 - batteryHealth: 98,
267 - mileage: 500,
268 - imageUrl: 'https://images.unsplash.com/photo-1558981403-c5f9899a28bc?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
269 - },
270 - {
271 - id: '4',
272 - name: '新日电动车',
273 - year: '2024年',
274 - school: '上海同济大学',
275 - price: 6700,
276 - batteryHealth: 96,
277 - mileage: 500,
278 - imageUrl: 'https://images.unsplash.com/photo-1595941069915-4ebc5197c14a?ixlib=rb-1.2.1&auto=format&fit=crop&w=800&q=60'
279 - }
280 -])
281 201
282 /** 202 /**
283 * 切换收藏状态 203 * 切换收藏状态
...@@ -296,14 +216,7 @@ const onProductClick = (scooter) => { ...@@ -296,14 +216,7 @@ const onProductClick = (scooter) => {
296 }) 216 })
297 } 217 }
298 218
299 -/** 219 +
300 - * 查看更多点击事件
301 - */
302 -const onMoreRecommendClick = () => {
303 - Taro.navigateTo({
304 - url: '/pages/recommendCarList/index'
305 - })
306 -}
307 220
308 // Menu组件事件处理方法 221 // Menu组件事件处理方法
309 /** 222 /**
......