hookehuyr

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

- 新增获取文章列表API接口用于首页轮播
- 替换mock数据为真实接口数据
- 优化轮播图组件支持不同类型内容展示
- 添加富文本内容处理函数适配图片宽度
/*
* @Date: 2025-07-09 14:58:51
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-16 15:37:53
* @LastEditTime: 2025-07-29 10:49:36
* @FilePath: /jgdl/src/api/car.js
* @Description: 车辆相关API接口
*/
......@@ -15,6 +15,7 @@ const Api = {
DETAIL_VEHICLE: '/srv/?a=vehicle&t=detail',
MY_LISTING_VEHICLE: '/srv/?a=vehicle&t=my_listings',
CHANGE_STATUS_VEHICLE: '/srv/?a=vehicle&t=change_status',
GET_ARTICLE_LIST: '/srv/?a=vehicle&t=article_list',
}
/**
......@@ -64,8 +65,8 @@ export const addVehicleAPI = (params) => fn(fetch.post(Api.ADD_VEHICLE, params))
export const editVehicleAPI = (params) => fn(fetch.post(Api.EDIT_VEHICLE, params));
/**
* @description: 首页轮播/最新上架/特价好车
* @param section 推荐的位置,1=首页轮播, 2=特价好车, 3=精品推荐
* @description: 最新上架/特价好车
* @param section 推荐的位置,2=特价好车, 3=精品推荐
* @param school_id 学校ID
* @param brand 品牌
* @param manufacture_year 出厂年份
......@@ -116,3 +117,10 @@ export const getVehicleDetailAPI = (params) => fn(fetch.get(Api.DETAIL_VEHICLE,
*/
export const changeVehicleStatusAPI = (params) => fn(fetch.post(Api.CHANGE_STATUS_VEHICLE, params));
/**
* @description: 首页轮播列表
* @returns data.list[{ id 文章ID, icon 封面图, post_title 文章标题, post_link 车辆ID, post_content 文章内容, type 类型(icon=只显示封面图,content=只显示文章详情,vehicle=只进入车辆详情页) }]
*/
export const getArticleListAPI = (params) => fn(fetch.get(Api.GET_ARTICLE_LIST, params));
......
<!--
* @Date: 2025-01-20 10:00:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-24 14:20:22
* @LastEditTime: 2025-07-29 12:41:42
* @FilePath: /jgdl/src/components/BannerSwiper.vue
* @Description: 轮播图组件
-->
......@@ -20,6 +20,7 @@
v-model:show="showImagePreview"
:images="previewImages"
:init-no="currentImageIndex"
:show-index="false"
/>
<!-- 文章弹框组件 -->
......@@ -31,10 +32,10 @@
:safe-area-inset-bottom="true"
>
<view class="article-modal">
<!-- <view class="article-header p-4 border-b">
<view class="article-header p-4 border-b border-gray-300" style="text-align: center;">
<text class="text-lg font-bold">{{ currentArticle.title }}</text>
</view> -->
<scroll-view :scroll-y="true" :catch-move="true" class="article-content" style="height: calc(100vh - 140rpx);">
</view>
<scroll-view :scroll-y="true" :catch-move="true" class="article-content" style="height: calc(100vh - 300rpx);">
<view class="p-4">
<rich-text :nodes="currentArticle.content"></rich-text>
</view>
......@@ -78,22 +79,22 @@ const currentArticle = ref({
*/
const onBannerClick = (item) => {
switch (item.type) {
case 'poster':
// 海报图 - 显示图片预览
previewImages.value = [item.image]
case 'icon':
// 只显示封面图 - 显示图片预览
previewImages.value = [{ src: item.image }]
currentImageIndex.value = 0
showImagePreview.value = true
break
case 'article':
// 文章封面图 - 显示文章弹框
case 'content':
// 只显示文章详情 - 显示文章弹框
currentArticle.value = {
title: item.title,
content: item.content
content: processRichTextContent(item.content)
}
showArticleModal.value = true
break
case 'vehicle':
// 车辆封面图 - 跳转到车辆详情页
// 只进入车辆详情页 - 跳转到车辆详情页
Taro.navigateTo({
url: `/pages/productDetail/index?id=${item.id}`
})
......@@ -107,6 +108,47 @@ const onBannerClick = (item) => {
}
/**
* 处理富文本内容,让图片适配容器宽度
* @param {string} content - 富文本内容
* @returns {string} 处理后的富文本内容
*/
const processRichTextContent = (content) => {
if (!content) return ''
// 移除img标签中的width和height属性,并添加style属性强制设置宽度为100%
let processedContent = content
// 移除width属性
.replace(/<img([^>]*?)\s+width\s*=\s*["'][^"']*["']([^>]*?)>/gi, '<img$1$2>')
// 移除height属性
.replace(/<img([^>]*?)\s+height\s*=\s*["'][^"']*["']([^>]*?)>/gi, '<img$1$2>')
// 移除现有的style属性中的width和height
.replace(/<img([^>]*?)\s+style\s*=\s*["']([^"']*?)["']([^>]*?)>/gi, (match, before, styleContent, after) => {
const cleanedStyle = styleContent
.replace(/width\s*:\s*[^;]+;?/gi, '')
.replace(/height\s*:\s*[^;]+;?/gi, '')
.replace(/;\s*;/g, ';')
.replace(/^\s*;|;\s*$/g, '')
return `<img${before} style="${cleanedStyle}"${after}>`
})
// 为所有img标签添加或更新style属性,设置宽度为100%
processedContent = processedContent.replace(/<img([^>]*?)>/gi, (match, attributes) => {
if (attributes.includes('style=')) {
// 如果已有style属性,在其中添加width: 100%
return match.replace(/style\s*=\s*["']([^"']*?)["']/gi, (styleMatch, styleContent) => {
const newStyle = styleContent ? `${styleContent}; width: 100%; height: auto;` : 'width: 100%; height: auto;'
return `style="${newStyle}"`
})
} else {
// 如果没有style属性,添加一个
return `<img${attributes} style="width: 100%; height: auto;">`
}
})
return processedContent
}
/**
* 关闭文章弹框
*/
const closeArticleModal = () => {
......@@ -114,7 +156,7 @@ const closeArticleModal = () => {
}
</script>
<style scoped>
<style lang="less">
.article-modal {
height: 100vh;
display: flex;
......@@ -122,18 +164,18 @@ const closeArticleModal = () => {
}
.article-content {
overflow-y: auto;
overflow: auto;
}
.article-footer {
position: sticky;
// position: sticky;
bottom: 0;
left: 0;
right: 0;
background: white;
border-top: 1px solid #eee;
// border-top: 1px solid #eee;
padding: 16px;
z-index: 1000;
margin-top: auto;
// margin-top: auto;
}
</style>
......
<!--
* @Date: 2025-06-28 10:33:00
* @LastEditors: hookehuyr hookehuyr@gmail.com
* @LastEditTime: 2025-07-28 15:54:33
* @LastEditTime: 2025-07-29 11:00:46
* @FilePath: /jgdl/src/pages/index/index.vue
* @Description: 捡个电驴首页
-->
......@@ -83,7 +83,7 @@ import LatestScooters from '@/components/LatestScooters.vue'
import BannerSwiper from '@/components/BannerSwiper.vue'
import "./index.less";
// 导入接口
import { getRecommendVehicleAPI } from '@/api/car';
import { getArticleListAPI } from '@/api/car';
// 响应式数据
const searchValue = ref('')
const showSearchPopup = ref(false)
......@@ -96,29 +96,6 @@ const onSearchHandle = () => {
// Banner数据
const bannerList = ref([])
// Mock轮播图数据
const mockBannerData = [
{
id: 1,
type: 'poster',
image: 'https://picsum.photos/400/160?random=1',
title: '海报图片'
},
{
id: 2,
type: 'article',
image: 'https://picsum.photos/400/160?random=2',
title: '电动车保养指南',
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>'
},
{
id: 3,
type: 'vehicle',
image: 'https://picsum.photos/400/160?random=3',
title: '精品二手车'
}
]
/**
* 点击认证车源
*/
......@@ -194,19 +171,21 @@ useReady(async () => {
onMounted(async () => {
// 获取首页轮播
const res1 = await getRecommendVehicleAPI({ section: 1 })
if (res1.code && res1.data.list.length) {
// 将API数据转换为车辆类型的轮播图
const vehicleBanners = res1.data.list.map(item => ({
id: item.id,
type: 'vehicle',
image: item.front_photo,
title: '精品二手车'
const { code, data } = await getArticleListAPI()
if (code && data.list.length) {
// 将API数据转换为轮播图格式
const articleBanners = data.list.map(item => ({
id: item.post_link || item.id, // 使用post_link作为车辆ID,如果没有则使用文章ID
type: item.type || 'icon', // 使用接口返回的type字段
image: item.icon, // 使用icon作为封面图
title: item.post_title, // 使用post_title作为标题
content: item.post_content // 使用post_content作为文章内容
}))
bannerList.value = vehicleBanners
bannerList.value = articleBanners
} else {
// 使用mock数据
// bannerList.value = mockBannerData
// API调用失败时设置为空数组
bannerList.value = []
console.warn('获取轮播图数据失败')
}
})
......