hookehuyr

feat(首页): 实现首页轮播图接口对接及组件优化

- 新增获取文章列表API接口用于首页轮播
- 替换mock数据为真实接口数据
- 优化轮播图组件支持不同类型内容展示
- 添加富文本内容处理函数适配图片宽度
1 /* 1 /*
2 * @Date: 2025-07-09 14:58:51 2 * @Date: 2025-07-09 14:58:51
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-16 15:37:53 4 + * @LastEditTime: 2025-07-29 10:49:36
5 * @FilePath: /jgdl/src/api/car.js 5 * @FilePath: /jgdl/src/api/car.js
6 * @Description: 车辆相关API接口 6 * @Description: 车辆相关API接口
7 */ 7 */
...@@ -15,6 +15,7 @@ const Api = { ...@@ -15,6 +15,7 @@ const Api = {
15 DETAIL_VEHICLE: '/srv/?a=vehicle&t=detail', 15 DETAIL_VEHICLE: '/srv/?a=vehicle&t=detail',
16 MY_LISTING_VEHICLE: '/srv/?a=vehicle&t=my_listings', 16 MY_LISTING_VEHICLE: '/srv/?a=vehicle&t=my_listings',
17 CHANGE_STATUS_VEHICLE: '/srv/?a=vehicle&t=change_status', 17 CHANGE_STATUS_VEHICLE: '/srv/?a=vehicle&t=change_status',
18 + GET_ARTICLE_LIST: '/srv/?a=vehicle&t=article_list',
18 } 19 }
19 20
20 /** 21 /**
...@@ -64,8 +65,8 @@ export const addVehicleAPI = (params) => fn(fetch.post(Api.ADD_VEHICLE, params)) ...@@ -64,8 +65,8 @@ export const addVehicleAPI = (params) => fn(fetch.post(Api.ADD_VEHICLE, params))
64 export const editVehicleAPI = (params) => fn(fetch.post(Api.EDIT_VEHICLE, params)); 65 export const editVehicleAPI = (params) => fn(fetch.post(Api.EDIT_VEHICLE, params));
65 66
66 /** 67 /**
67 - * @description: 首页轮播/最新上架/特价好车 68 + * @description: 最新上架/特价好车
68 - * @param section 推荐的位置,1=首页轮播, 2=特价好车, 3=精品推荐 69 + * @param section 推荐的位置,2=特价好车, 3=精品推荐
69 * @param school_id 学校ID 70 * @param school_id 学校ID
70 * @param brand 品牌 71 * @param brand 品牌
71 * @param manufacture_year 出厂年份 72 * @param manufacture_year 出厂年份
...@@ -116,3 +117,10 @@ export const getVehicleDetailAPI = (params) => fn(fetch.get(Api.DETAIL_VEHICLE, ...@@ -116,3 +117,10 @@ export const getVehicleDetailAPI = (params) => fn(fetch.get(Api.DETAIL_VEHICLE,
116 */ 117 */
117 118
118 export const changeVehicleStatusAPI = (params) => fn(fetch.post(Api.CHANGE_STATUS_VEHICLE, params)); 119 export const changeVehicleStatusAPI = (params) => fn(fetch.post(Api.CHANGE_STATUS_VEHICLE, params));
120 +
121 +/**
122 + * @description: 首页轮播列表
123 + * @returns data.list[{ id 文章ID, icon 封面图, post_title 文章标题, post_link 车辆ID, post_content 文章内容, type 类型(icon=只显示封面图,content=只显示文章详情,vehicle=只进入车辆详情页) }]
124 + */
125 +
126 +export const getArticleListAPI = (params) => fn(fetch.get(Api.GET_ARTICLE_LIST, params));
......
1 <!-- 1 <!--
2 * @Date: 2025-01-20 10:00:00 2 * @Date: 2025-01-20 10:00:00
3 * @LastEditors: hookehuyr hookehuyr@gmail.com 3 * @LastEditors: hookehuyr hookehuyr@gmail.com
4 - * @LastEditTime: 2025-07-24 14:20:22 4 + * @LastEditTime: 2025-07-29 12:41:42
5 * @FilePath: /jgdl/src/components/BannerSwiper.vue 5 * @FilePath: /jgdl/src/components/BannerSwiper.vue
6 * @Description: 轮播图组件 6 * @Description: 轮播图组件
7 --> 7 -->
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
20 v-model:show="showImagePreview" 20 v-model:show="showImagePreview"
21 :images="previewImages" 21 :images="previewImages"
22 :init-no="currentImageIndex" 22 :init-no="currentImageIndex"
23 + :show-index="false"
23 /> 24 />
24 25
25 <!-- 文章弹框组件 --> 26 <!-- 文章弹框组件 -->
...@@ -31,10 +32,10 @@ ...@@ -31,10 +32,10 @@
31 :safe-area-inset-bottom="true" 32 :safe-area-inset-bottom="true"
32 > 33 >
33 <view class="article-modal"> 34 <view class="article-modal">
34 - <!-- <view class="article-header p-4 border-b"> 35 + <view class="article-header p-4 border-b border-gray-300" style="text-align: center;">
35 <text class="text-lg font-bold">{{ currentArticle.title }}</text> 36 <text class="text-lg font-bold">{{ currentArticle.title }}</text>
36 - </view> --> 37 + </view>
37 - <scroll-view :scroll-y="true" :catch-move="true" class="article-content" style="height: calc(100vh - 140rpx);"> 38 + <scroll-view :scroll-y="true" :catch-move="true" class="article-content" style="height: calc(100vh - 300rpx);">
38 <view class="p-4"> 39 <view class="p-4">
39 <rich-text :nodes="currentArticle.content"></rich-text> 40 <rich-text :nodes="currentArticle.content"></rich-text>
40 </view> 41 </view>
...@@ -78,22 +79,22 @@ const currentArticle = ref({ ...@@ -78,22 +79,22 @@ const currentArticle = ref({
78 */ 79 */
79 const onBannerClick = (item) => { 80 const onBannerClick = (item) => {
80 switch (item.type) { 81 switch (item.type) {
81 - case 'poster': 82 + case 'icon':
82 - // 海报图 - 显示图片预览 83 + // 只显示封面图 - 显示图片预览
83 - previewImages.value = [item.image] 84 + previewImages.value = [{ src: item.image }]
84 currentImageIndex.value = 0 85 currentImageIndex.value = 0
85 showImagePreview.value = true 86 showImagePreview.value = true
86 break 87 break
87 - case 'article': 88 + case 'content':
88 - // 文章封面图 - 显示文章弹框 89 + // 只显示文章详情 - 显示文章弹框
89 currentArticle.value = { 90 currentArticle.value = {
90 title: item.title, 91 title: item.title,
91 - content: item.content 92 + content: processRichTextContent(item.content)
92 } 93 }
93 showArticleModal.value = true 94 showArticleModal.value = true
94 break 95 break
95 case 'vehicle': 96 case 'vehicle':
96 - // 车辆封面图 - 跳转到车辆详情页 97 + // 只进入车辆详情页 - 跳转到车辆详情页
97 Taro.navigateTo({ 98 Taro.navigateTo({
98 url: `/pages/productDetail/index?id=${item.id}` 99 url: `/pages/productDetail/index?id=${item.id}`
99 }) 100 })
...@@ -107,6 +108,47 @@ const onBannerClick = (item) => { ...@@ -107,6 +108,47 @@ const onBannerClick = (item) => {
107 } 108 }
108 109
109 /** 110 /**
111 + * 处理富文本内容,让图片适配容器宽度
112 + * @param {string} content - 富文本内容
113 + * @returns {string} 处理后的富文本内容
114 + */
115 +const processRichTextContent = (content) => {
116 + if (!content) return ''
117 +
118 + // 移除img标签中的width和height属性,并添加style属性强制设置宽度为100%
119 + let processedContent = content
120 + // 移除width属性
121 + .replace(/<img([^>]*?)\s+width\s*=\s*["'][^"']*["']([^>]*?)>/gi, '<img$1$2>')
122 + // 移除height属性
123 + .replace(/<img([^>]*?)\s+height\s*=\s*["'][^"']*["']([^>]*?)>/gi, '<img$1$2>')
124 + // 移除现有的style属性中的width和height
125 + .replace(/<img([^>]*?)\s+style\s*=\s*["']([^"']*?)["']([^>]*?)>/gi, (match, before, styleContent, after) => {
126 + const cleanedStyle = styleContent
127 + .replace(/width\s*:\s*[^;]+;?/gi, '')
128 + .replace(/height\s*:\s*[^;]+;?/gi, '')
129 + .replace(/;\s*;/g, ';')
130 + .replace(/^\s*;|;\s*$/g, '')
131 + return `<img${before} style="${cleanedStyle}"${after}>`
132 + })
133 +
134 + // 为所有img标签添加或更新style属性,设置宽度为100%
135 + processedContent = processedContent.replace(/<img([^>]*?)>/gi, (match, attributes) => {
136 + if (attributes.includes('style=')) {
137 + // 如果已有style属性,在其中添加width: 100%
138 + return match.replace(/style\s*=\s*["']([^"']*?)["']/gi, (styleMatch, styleContent) => {
139 + const newStyle = styleContent ? `${styleContent}; width: 100%; height: auto;` : 'width: 100%; height: auto;'
140 + return `style="${newStyle}"`
141 + })
142 + } else {
143 + // 如果没有style属性,添加一个
144 + return `<img${attributes} style="width: 100%; height: auto;">`
145 + }
146 + })
147 +
148 + return processedContent
149 +}
150 +
151 +/**
110 * 关闭文章弹框 152 * 关闭文章弹框
111 */ 153 */
112 const closeArticleModal = () => { 154 const closeArticleModal = () => {
...@@ -114,7 +156,7 @@ const closeArticleModal = () => { ...@@ -114,7 +156,7 @@ const closeArticleModal = () => {
114 } 156 }
115 </script> 157 </script>
116 158
117 -<style scoped> 159 +<style lang="less">
118 .article-modal { 160 .article-modal {
119 height: 100vh; 161 height: 100vh;
120 display: flex; 162 display: flex;
...@@ -122,18 +164,18 @@ const closeArticleModal = () => { ...@@ -122,18 +164,18 @@ const closeArticleModal = () => {
122 } 164 }
123 165
124 .article-content { 166 .article-content {
125 - overflow-y: auto; 167 + overflow: auto;
126 } 168 }
127 169
128 .article-footer { 170 .article-footer {
129 - position: sticky; 171 + // position: sticky;
130 bottom: 0; 172 bottom: 0;
131 left: 0; 173 left: 0;
132 right: 0; 174 right: 0;
133 background: white; 175 background: white;
134 - border-top: 1px solid #eee; 176 + // border-top: 1px solid #eee;
135 padding: 16px; 177 padding: 16px;
136 z-index: 1000; 178 z-index: 1000;
137 - margin-top: auto; 179 + // margin-top: auto;
138 } 180 }
139 </style> 181 </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-28 15:54:33 4 + * @LastEditTime: 2025-07-29 11:00:46
5 * @FilePath: /jgdl/src/pages/index/index.vue 5 * @FilePath: /jgdl/src/pages/index/index.vue
6 * @Description: 捡个电驴首页 6 * @Description: 捡个电驴首页
7 --> 7 -->
...@@ -83,7 +83,7 @@ import LatestScooters from '@/components/LatestScooters.vue' ...@@ -83,7 +83,7 @@ import LatestScooters from '@/components/LatestScooters.vue'
83 import BannerSwiper from '@/components/BannerSwiper.vue' 83 import BannerSwiper from '@/components/BannerSwiper.vue'
84 import "./index.less"; 84 import "./index.less";
85 // 导入接口 85 // 导入接口
86 -import { getRecommendVehicleAPI } from '@/api/car'; 86 +import { getArticleListAPI } from '@/api/car';
87 // 响应式数据 87 // 响应式数据
88 const searchValue = ref('') 88 const searchValue = ref('')
89 const showSearchPopup = ref(false) 89 const showSearchPopup = ref(false)
...@@ -96,29 +96,6 @@ const onSearchHandle = () => { ...@@ -96,29 +96,6 @@ const onSearchHandle = () => {
96 // Banner数据 96 // Banner数据
97 const bannerList = ref([]) 97 const bannerList = ref([])
98 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 -]
121 -
122 /** 99 /**
123 * 点击认证车源 100 * 点击认证车源
124 */ 101 */
...@@ -194,19 +171,21 @@ useReady(async () => { ...@@ -194,19 +171,21 @@ useReady(async () => {
194 171
195 onMounted(async () => { 172 onMounted(async () => {
196 // 获取首页轮播 173 // 获取首页轮播
197 - const res1 = await getRecommendVehicleAPI({ section: 1 }) 174 + const { code, data } = await getArticleListAPI()
198 - if (res1.code && res1.data.list.length) { 175 + if (code && data.list.length) {
199 - // 将API数据转换为车辆类型的轮播图 176 + // 将API数据转换为轮播图格式
200 - const vehicleBanners = res1.data.list.map(item => ({ 177 + const articleBanners = data.list.map(item => ({
201 - id: item.id, 178 + id: item.post_link || item.id, // 使用post_link作为车辆ID,如果没有则使用文章ID
202 - type: 'vehicle', 179 + type: item.type || 'icon', // 使用接口返回的type字段
203 - image: item.front_photo, 180 + image: item.icon, // 使用icon作为封面图
204 - title: '精品二手车' 181 + title: item.post_title, // 使用post_title作为标题
182 + content: item.post_content // 使用post_content作为文章内容
205 })) 183 }))
206 - bannerList.value = vehicleBanners 184 + bannerList.value = articleBanners
207 } else { 185 } else {
208 - // 使用mock数据 186 + // API调用失败时设置为空数组
209 - // bannerList.value = mockBannerData 187 + bannerList.value = []
188 + console.warn('获取轮播图数据失败')
210 } 189 }
211 }) 190 })
212 191
......