hookehuyr

feat(home): 添加轮播图组件并重构首页banner逻辑

重构首页banner展示逻辑,将轮播图抽离为独立组件BannerSwiper
新增支持多种类型的banner点击处理(图片预览、文章弹窗、车辆详情)
添加mock数据作为banner备用数据源
移除不再使用的console.warn调试代码
...@@ -7,6 +7,7 @@ export {} ...@@ -7,6 +7,7 @@ export {}
7 7
8 declare module 'vue' { 8 declare module 'vue' {
9 export interface GlobalComponents { 9 export interface GlobalComponents {
10 + BannerSwiper: typeof import('./src/components/BannerSwiper.vue')['default']
10 BrandModelPicker: typeof import('./src/components/BrandModelPicker.vue')['default'] 11 BrandModelPicker: typeof import('./src/components/BrandModelPicker.vue')['default']
11 FeaturedRecommendations: typeof import('./src/components/FeaturedRecommendations.vue')['default'] 12 FeaturedRecommendations: typeof import('./src/components/FeaturedRecommendations.vue')['default']
12 LatestScooters: typeof import('./src/components/LatestScooters.vue')['default'] 13 LatestScooters: typeof import('./src/components/LatestScooters.vue')['default']
......
1 +<!--
2 + * @Date: 2025-01-20 10:00:00
3 + * @LastEditors: hookehuyr hookehuyr@gmail.com
4 + * @LastEditTime: 2025-07-24 14:20:22
5 + * @FilePath: /jgdl/src/components/BannerSwiper.vue
6 + * @Description: 轮播图组件
7 +-->
8 +<template>
9 + <view class="px-4 pt-4" style="background: linear-gradient( 180deg, #fb923c 0%, rgba(255,203,53,0) 61%);">
10 + <nut-swiper :init-page="0" :pagination-visible="true" pagination-color="#ffffff" auto-play="3000"
11 + class="rounded-lg overflow-hidden" height="160">
12 + <nut-swiper-item v-for="(item, index) in bannerList" :key="index" @click="onBannerClick(item)">
13 + <image :src="item.image" mode="aspectFill" class="w-full h-40 object-cover" />
14 + </nut-swiper-item>
15 + </nut-swiper>
16 + </view>
17 +
18 + <!-- 图片预览组件 -->
19 + <nut-image-preview
20 + v-model:show="showImagePreview"
21 + :images="previewImages"
22 + :init-no="currentImageIndex"
23 + />
24 +
25 + <!-- 文章弹框组件 -->
26 + <nut-popup
27 + v-model:visible="showArticleModal"
28 + position="bottom"
29 + :style="{ height: '100vh' }"
30 + close-icon-position="bottom-right"
31 + :safe-area-inset-bottom="true"
32 + >
33 + <view class="article-modal">
34 + <!-- <view class="article-header p-4 border-b">
35 + <text class="text-lg font-bold">{{ currentArticle.title }}</text>
36 + </view> -->
37 + <scroll-view :scroll-y="true" :catch-move="true" class="article-content" style="height: calc(100vh - 140rpx);">
38 + <view class="p-4">
39 + <rich-text :nodes="currentArticle.content"></rich-text>
40 + </view>
41 + </scroll-view>
42 + <view class="article-footer p-4">
43 + <nut-button type="primary" block color="#fb923c" @click="closeArticleModal">
44 + 关闭
45 + </nut-button>
46 + </view>
47 + </view>
48 + </nut-popup>
49 +</template>
50 +
51 +<script setup>
52 +import Taro from '@tarojs/taro'
53 +import { ref } from 'vue'
54 +
55 +// 定义props
56 +defineProps({
57 + bannerList: {
58 + type: Array,
59 + default: () => []
60 + }
61 +})
62 +
63 +// 定义emits
64 +const emit = defineEmits(['bannerClick'])
65 +
66 +// 响应式数据
67 +const showImagePreview = ref(false)
68 +const previewImages = ref([])
69 +const currentImageIndex = ref(0)
70 +const showArticleModal = ref(false)
71 +const currentArticle = ref({
72 + title: '',
73 + content: ''
74 +})
75 +
76 +/**
77 + * 点击轮播图处理函数
78 + */
79 +const onBannerClick = (item) => {
80 + switch (item.type) {
81 + case 'poster':
82 + // 海报图 - 显示图片预览
83 + previewImages.value = [item.image]
84 + currentImageIndex.value = 0
85 + showImagePreview.value = true
86 + break
87 + case 'article':
88 + // 文章封面图 - 显示文章弹框
89 + currentArticle.value = {
90 + title: item.title,
91 + content: item.content
92 + }
93 + showArticleModal.value = true
94 + break
95 + case 'vehicle':
96 + // 车辆封面图 - 跳转到车辆详情页
97 + Taro.navigateTo({
98 + url: `/pages/productDetail/index?id=${item.id}`
99 + })
100 + break
101 + default:
102 + console.warn('未知的轮播图类型:', item.type)
103 + }
104 +
105 + // 触发父组件事件
106 + emit('bannerClick', item)
107 +}
108 +
109 +/**
110 + * 关闭文章弹框
111 + */
112 +const closeArticleModal = () => {
113 + showArticleModal.value = false
114 +}
115 +</script>
116 +
117 +<style scoped>
118 +.article-modal {
119 + height: 100vh;
120 + display: flex;
121 + flex-direction: column;
122 +}
123 +
124 +.article-content {
125 + overflow-y: auto;
126 +}
127 +
128 +.article-footer {
129 + position: sticky;
130 + bottom: 0;
131 + left: 0;
132 + right: 0;
133 + background: white;
134 + border-top: 1px solid #eee;
135 + padding: 16px;
136 + z-index: 1000;
137 + margin-top: auto;
138 +}
139 +</style>
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-18 17:11:10 4 + * @LastEditTime: 2025-07-24 14:21:19
5 * @FilePath: /jgdl/src/pages/index/index.vue 5 * @FilePath: /jgdl/src/pages/index/index.vue
6 * @Description: 捡个电驴首页 6 * @Description: 捡个电驴首页
7 --> 7 -->
...@@ -28,14 +28,7 @@ ...@@ -28,14 +28,7 @@
28 </nut-sticky> 28 </nut-sticky>
29 29
30 <!-- Banner --> 30 <!-- Banner -->
31 - <view class="px-4 pt-4" style="background: linear-gradient( 180deg, #fb923c 0%, rgba(255,203,53,0) 61%);"> 31 + <BannerSwiper :banner-list="bannerList" />
32 - <nut-swiper :init-page="0" :pagination-visible="true" pagination-color="#ffffff" auto-play="3000"
33 - class="rounded-lg overflow-hidden" height="160">
34 - <nut-swiper-item v-for="(image, index) in bannerImages" :key="index" @click="onBannerClick(image)">
35 - <image :src="image.front_photo" mode="aspectFill" class="w-full h-40 object-cover" />
36 - </nut-swiper-item>
37 - </nut-swiper>
38 - </view>
39 32
40 <!-- Category Icons --> 33 <!-- Category Icons -->
41 <view class="px-4 mt-2"> 34 <view class="px-4 mt-2">
...@@ -87,10 +80,10 @@ import TabBar from '@/components/TabBar.vue' ...@@ -87,10 +80,10 @@ import TabBar from '@/components/TabBar.vue'
87 import SearchPopup from '@/components/SearchPopup.vue' 80 import SearchPopup from '@/components/SearchPopup.vue'
88 import FeaturedRecommendations from '@/components/FeaturedRecommendations.vue' 81 import FeaturedRecommendations from '@/components/FeaturedRecommendations.vue'
89 import LatestScooters from '@/components/LatestScooters.vue' 82 import LatestScooters from '@/components/LatestScooters.vue'
83 +import BannerSwiper from '@/components/BannerSwiper.vue'
90 import "./index.less"; 84 import "./index.less";
91 // 导入接口 85 // 导入接口
92 import { getRecommendVehicleAPI } from '@/api/car'; 86 import { getRecommendVehicleAPI } from '@/api/car';
93 -import { DEFAULT_COVER_IMG } from '@/utils/config'
94 // 响应式数据 87 // 响应式数据
95 const searchValue = ref('') 88 const searchValue = ref('')
96 const showSearchPopup = ref(false) 89 const showSearchPopup = ref(false)
...@@ -100,8 +93,31 @@ const onSearchHandle = () => { ...@@ -100,8 +93,31 @@ const onSearchHandle = () => {
100 showSearchPopup.value = true 93 showSearchPopup.value = true
101 } 94 }
102 95
103 -// Banner图片 96 +// Banner数据
104 -const bannerImages = ref([]) 97 +const bannerList = ref([])
98 +
99 +// Mock轮播图数据
100 +const mockBannerData = [
101 + {
102 + id: 1,
103 + type: 'poster',
104 + image: 'https://picsum.photos/400/160?random=1',
105 + title: '海报图片'
106 + },
107 + {
108 + id: 2,
109 + type: 'article',
110 + image: 'https://picsum.photos/400/160?random=2',
111 + title: '电动车保养指南',
112 + content: '<h2>电动车保养指南</h2><p>电动车作为现代出行的重要工具,正确的保养方式能够延长其使用寿命。</p><h3>1. 电池保养</h3><p>• 避免过度充电和过度放电</p><p>• 定期检查电池连接线是否松动</p><p>• 保持电池清洁干燥</p><h3>2. 轮胎保养</h3><p>• 定期检查轮胎气压</p><p>• 检查轮胎磨损情况</p><p>• 及时更换磨损严重的轮胎</p><h3>3. 刹车系统</h3><p>• 定期检查刹车片厚度</p><p>• 检查刹车线是否正常</p><p>• 保持刹车系统清洁</p><p>通过以上保养措施,您的电动车将为您提供更安全、更持久的服务。</p><p>电动车作为现代出行的重要工具,正确的保养方式能够延长其使用寿命。</p><h3>1. 电池保养</h3><p>• 避免过度充电和过度放电</p><p>• 定期检查电池连接线是否松动</p><p>• 保持电池清洁干燥</p><h3>2. 轮胎保养</h3><p>• 定期检查轮胎气压</p><p>• 检查轮胎磨损情况</p><p>• 及时更换磨损严重的轮胎</p><h3>3. 刹车系统</h3><p>• 定期检查刹车片厚度</p><p>• 检查刹车线是否正常</p><p>• 保持刹车系统清洁</p><p>通过以上保养措施,您的电动车将为您提供更安全、更持久的服务。</p>'
113 + },
114 + {
115 + id: 3,
116 + type: 'vehicle',
117 + image: 'https://picsum.photos/400/160?random=3',
118 + title: '精品二手车'
119 + }
120 +]
105 121
106 /** 122 /**
107 * 点击认证车源 123 * 点击认证车源
...@@ -179,17 +195,18 @@ useReady(async () => { ...@@ -179,17 +195,18 @@ useReady(async () => {
179 onMounted(async () => { 195 onMounted(async () => {
180 // 获取首页轮播 196 // 获取首页轮播
181 const res1 = await getRecommendVehicleAPI({ section: 1 }) 197 const res1 = await getRecommendVehicleAPI({ section: 1 })
182 - if (res1.code) { 198 + if (res1.code && res1.data.list.length) {
183 - bannerImages.value = res1.data.list.map(item => ({ 199 + // 将API数据转换为车辆类型的轮播图
200 + const vehicleBanners = res1.data.list.map(item => ({
184 id: item.id, 201 id: item.id,
185 - front_photo: item.front_photo 202 + type: 'vehicle',
186 - })); 203 + image: item.front_photo,
187 - if (!bannerImages.value.length) { 204 + title: '精品二手车'
188 - bannerImages.value = [{ 205 + }))
189 - id: 0, 206 + bannerList.value = vehicleBanners
190 - front_photo: DEFAULT_COVER_IMG 207 + } else {
191 - }] 208 + // 使用mock数据
192 - } 209 + bannerList.value = mockBannerData
193 } 210 }
194 }) 211 })
195 212
...@@ -217,10 +234,5 @@ useShareAppMessage(() => { ...@@ -217,10 +234,5 @@ useShareAppMessage(() => {
217 return shareObj; 234 return shareObj;
218 }) 235 })
219 236
220 -// 点击banner图片跳转到详情页 237 +
221 -const onBannerClick = ({id}) => {
222 - Taro.navigateTo({
223 - url: `/pages/productDetail/index?id=${id}`
224 - })
225 -}
226 </script> 238 </script>
......
...@@ -119,7 +119,6 @@ const scrollTop = ref(0) ...@@ -119,7 +119,6 @@ const scrollTop = ref(0)
119 // 搜索值 119 // 搜索值
120 const searchValue = ref('') 120 const searchValue = ref('')
121 const onBlurSearch = async () => { 121 const onBlurSearch = async () => {
122 - console.warn(searchValue.value)
123 // 重置分页状态 122 // 重置分页状态
124 page.value = 1 123 page.value = 1
125 hasMore.value = true 124 hasMore.value = true
......