feat(首页): 实现首页轮播图接口对接及组件优化
- 新增获取文章列表API接口用于首页轮播 - 替换mock数据为真实接口数据 - 优化轮播图组件支持不同类型内容展示 - 添加富文本内容处理函数适配图片宽度
Showing
3 changed files
with
84 additions
and
55 deletions
| 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 | ... | ... |
-
Please register or login to post a comment