From 500078714411487af00161e01bd7e0b5efdc3414 Mon Sep 17 00:00:00 2001 From: cloudroam <cloudroam> Date: 星期四, 07 八月 2025 13:32:32 +0800 Subject: [PATCH] add:热门景点 --- pages/home/home.vue | 395 +++++++++++++++++++++++++++++++++++++++++--------------- 1 files changed, 288 insertions(+), 107 deletions(-) diff --git a/pages/home/home.vue b/pages/home/home.vue index bea0cf1..353f27b 100644 --- a/pages/home/home.vue +++ b/pages/home/home.vue @@ -23,26 +23,53 @@ </view> <view class="trip-card-swiper"> - <swiper :current="currentPage" @change="onSwiperChange" circular style="min-height: 1410rpx;"> + <swiper :current="currentPage" @change="onSwiperChange" circular style="min-height: 1650rpx;"> <swiper-item v-for="(group, pageIndex) in pagedTripCards" :key="pageIndex"> - <TripCard v-for="(item, index) in group" :key="index" :tag="item.tag" :title="item.title" - :subtitle="item.subtitle" :score="item.score" :imageUrl="item.imageUrl" :detailUrl="item.detailUrl" /> + <TripCard v-for="(item, index) in group" :key="index" :tag="item.tag" :title="item.coverTitle" + :subtitle="item.coverAlt" :score="item.collectCount" :imageUrl="item.coverUrl" + :detailUrl="`${detailUrl}?id=${item.id}`" /> </swiper-item> </swiper> </view> <SectionTitle title="全球影视地标" optitle="查看全部" goUrl="/pages/home/home-more" /> - <GlobalGeo /> +<!-- <GlobalGeo />--> + <view class="continent-section"> + <view class="continent-item all" @click="navigateToAll">全部</view> + <view class="continents-container"> + <view class="continent-row" v-for="(row, index) in continentRows" :key="index"> + <view + class="continent-item continent" + v-for="(continent, colIndex) in row" + :key="colIndex" + @click="navigateToDetail(continent)" + > + {{ continent.name }} + </view> + <!-- 添加占位元素保持布局 --> + <view + v-for="n in (3 - row.length)" + :key="'placeholder'+n" + class="continent-placeholder" + ></view> + </view> + </view> + <view class="continent-item separator"></view> + <view class="continent-item nearby" @click="navigateToNearby"> + <view>附</view> + <view>近</view> + </view> + </view> - <SectionTitle title="场景博物馆" optitle="查看全部" goUrl="/pages/home/home-more" /> + <SectionTitle title="场景博物馆" optitle="查看全部" goUrl="/sub-pages/hot-spot/index" /> <SceneMuseumCard v-for="(item, index) in cardList" :key="index" :image="item.image" :title="item.title" :subtitle="item.subtitle" :readTime="item.readTime" /> - <SectionTitle title="光影社区" optitle="加入社区" goUrl="/pages/home/home-more" /> - <Community v-for="(item, index) in communitys" :key="index" :avatar="item.avatar" :nickname="item.nickname" - :time="item.time" :image="item.image" :content="item.content" :likeCount="item.likeCount" - :commentCount="item.commentCount" /> - + <SectionTitle title="光影天地" optitle="加入光影" goUrl="/sub-pages/community/index" /> + <Community v-for="(item, index) in communitys" :key="index" :detailUrl="`${detailUrl}?id=${item.id}`" :avatar="item.avatar" :nickname="item.nickname" + :time="formatRelativeTime(item.createTime)" :image="item.coverUrl" :content="item.coverAlt" + :likeCount="item.likeCount" :commentCount="item.commentCount" /> + <up-loadmore :status="communityStatus" :line="true" /> <view style="height: 300rpx;"></view> </view> @@ -52,18 +79,70 @@ <script setup lang="ts"> import { ref, computed, onMounted } from 'vue' +import { onShow, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app' import HomeMain from './home-main.vue' import TripCard from './trip-card.vue' -import GlobalGeo from './global-geo.vue' import SceneMuseumCard from './scene-museum-card.vue' import Community from './community.vue' - import { SwiperChangeEvent } from '@dcloudio/uni-app' +import { useGlobal } from '@/composables/useGlobal' +const { $http, $message, $store } = useGlobal() +import { FilmWorks } from '@/types/index' +import { formatRelativeTime } from '@/utils/time' +import { FilmWorksCategory } from '@/enums/dict' // 主题 const theme = ref('light') + +const continents = ref([]) + +const getContinents = async () => { + try { + const {code, data} = await $http.request('get', '/api/code/value?type=CONTINENT_TYPE') + if (code == 0 && data) { + continents.value = data.map(item => ( + { + id: item.id, + name: item.label + }) + ) + } + + } catch (error) { + console.log('获取洲数据失败', error) + $message.showToast('获取洲数据失败') + } + +} + +const continentRows = computed(() => { + const rows = []; + const continentsList = [...continents.value]; + + // 将洲分成三行 + for (let i = 0; i < 3; i++) { + const start = i * 3; + const end = start + 3; + rows.push(continentsList.slice(start, end)); + } + + return rows; +}); + +const navigateToDetail = (continent) => { + //跳转到具体页面 + uni.navigateTo({ + url: `/sub-pages/hot-city/index?continentId=${continent.id}` + }) +} +const navigateToNearby = () => { + //附近功能跳转 + uni.navigateTo({ + url: '/pages/nearby/nearby' + }) +} // 当前页数 @@ -71,119 +150,145 @@ // 每页显示条数 const pageSize = 3 + // 旅行卡片数据 -const tripCardList = ref([ - { - tag: '史诗邮轮', - title: '《权力的游戏》君临城朝圣之旅', - subtitle: '克罗地亚杜布罗夫尼克5日深度游', - score: 4.9, - imageUrl: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', - detailUrl: '/pages/detail?id=123', - }, - { - tag: '经典路线', - title: '冰岛极光探索之旅', - subtitle: '冬季极地风光6日深度体验', - score: 4.8, - imageUrl: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', - detailUrl: '/pages/detail?id=124', - }, - { - tag: '文化探秘', - title: '日本京都文化巡游', - subtitle: '探访古都名刹5日行程', - score: 4.7, - imageUrl: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', - detailUrl: '/pages/detail?id=125', - }, - { - tag: '史诗邮轮2', - title: '《权力的游戏》君临城朝圣之旅', - subtitle: '克罗地亚杜布罗夫尼克5日深度游', - score: 4.9, - imageUrl: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', - detailUrl: '/pages/detail?id=123', - }, - { - tag: '经典路线2', - title: '冰岛极光探索之旅', - subtitle: '冬季极地风光6日深度体验', - score: 4.8, - imageUrl: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', - detailUrl: '/pages/detail?id=124', - }, - { - tag: '文化探秘2', - title: '日本京都文化巡游', - subtitle: '探访古都名刹5日行程', - score: 4.7, - imageUrl: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', - detailUrl: '/pages/detail?id=125', - }, -]) +const detailUrl = '/sub-pages/film-list/film-detail' +const tripCardList = ref<FilmWorks[]>([]) // 分页后的数组,每页3条 const pagedTripCards = computed(() => { - const pages = [] - for (let i = 0; i < tripCardList.value.length; i += pageSize) { - pages.push(tripCardList.value.slice(i, i + pageSize)) + const pages: FilmWorks[][] = [] + const list = tripCardList.value || [] // 安全兜底 + for (let i = 0; i < list.length; i += pageSize) { + pages.push(list.slice(i, i + pageSize)) } return pages }) // 总页数 -const totalPages = computed(() => Math.ceil(tripCardList.value.length / pageSize)) +const totalPages = computed(() => { + const list = tripCardList.value || [] // 安全兜底 + return Math.ceil(list.length / pageSize) +}) // 场景博物馆卡片数据 -const cardList = ref([ - { - image: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', - title: '《盗梦空间》巴黎咖啡馆', - subtitle: '拷素诺兰如何创造这个标志性场景', - readTime: '12分钟阅读', - }, - { - image: 'https://img.yzcdn.cn/vant/cat.jpeg', - title: '《星际穿越》玉米田', - subtitle: '诺兰如何还原地球末日场景', - readTime: '8分钟阅读', - }, - { - image: 'https://img.yzcdn.cn/vant/cat.jpeg', - title: '《星球大战》塔图因星球', - subtitle: '经典科幻电影中的沙漠设定', - readTime: '10分钟阅读', - }, -]) +// 场景博物馆卡片数据 +const cardList = ref<Array<{ + image: string + title: string + subtitle: string + readTime: string +}>>([]) -// 社区帖子数据 -const communitys = ref([ - { - avatar: 'https://cdn.uviewui.com/uview/common/logo.png', - nickname: '电影探索者', - time: '2天前', - image: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', - content: '终于站在了《权游》中君临城的台阶上!这里的每一块石头都仿佛在讲述故事...', - likeCount: 128, - commentCount: 24, - }, - { - avatar: 'https://cdn.uviewui.com/uview/common/logo.png', - nickname: '美食家', - time: '1天前', - image: 'https://ai-public.mastergo.com/ai/img_res/6a226f9e9652c51cd535c3490535dfeb.jpg', - content: '这家巴黎咖啡馆的氛围真是太棒了,感觉回到了电影里的场景。', - likeCount: 76, - commentCount: 12, - }, -]) +// 数据 +const communitys = ref<FilmWorks[]>([]) + // 生命周期 onMounted(() => { const localTheme = uni.getStorageSync('theme') || 'light' theme.value = localTheme + getContinents() }) + +onShow(() => { + + // 内容精选 + getContentSelected() + + // 光影 + getCommunitys() + + // 场景博物馆 + getSceneMuseumData() +}); + + +onPullDownRefresh(async () => { + console.log('用户下拉刷新了') + // 内容精选 + getContentSelected() + // 光影 + communityPage.value = 1 + getCommunitys() + uni.stopPullDownRefresh() // 停止下拉刷新动画 +}) + +onReachBottom(() => { + console.log('用户触底了') + getCommunitys() +}) + +const getContentSelected = async () => { + tripCardList.value = await getFilmWorks(FilmWorksCategory.CONTENT_SELECTED, 20, 1); +} + + +const communityPage = ref(1) +const communitySize = 10 +const communityStatus = ref('loading') +const getCommunitys = async () => { + if (communityStatus.value === 'nomore') return + + communityStatus.value = 'loading' + + const records = await getFilmWorks(FilmWorksCategory.COMMUNITY, communitySize, communityPage.value) + + if (records && records.length > 0) { + // 使用 Set 进行去重 + const existingIds = new Set(communitys.value.map(item => item.id)) + const uniqueRecords = records.filter(item => !existingIds.has(item.id)) + + communitys.value = [...communitys.value, ...uniqueRecords] + + // 如果返回的记录数少于请求的 size,说明没有更多数据 + if (records.length < communitySize) { + communityStatus.value = 'noMore' + } + + communityPage.value++ + } else { + communityStatus.value = 'noMore' + } +} + + +// 获取场景博物馆数据 +const getSceneMuseumData = async () => { + try { + const { code, data } = await $http.request('get', '/api/filmLocation/getTop3') + if (code === 0 && data) { + cardList.value = data.map(item => ({ + image: item.locationUrl, + title: item.locationName, + subtitle: item.address, + readTime: `${item.locationWeight}热度` + })) + } + } catch (error) { + console.error('获取场景博物馆数据失败:', error) + $message.showToast('获取场景博物馆数据失败') + } +} +// 内容精选 +const getFilmWorks = async (type: String, pageSize: Number, currentPage: Number) => { + const { + code, data + } = await $http.request('get', '/api/filmWorks/list', { + params: { + classify: type, + size: pageSize, + status: 'published', + current: currentPage + } + }) + if (code == 0) { + return data.records + } else { + $message.showToast('系统异常,无法获取数据') + return null; + } +} // 下一页 const nextPage = () => { @@ -224,6 +329,82 @@ margin-top: 20rpx; margin-bottom: 20rpx; } + +.continent-section { + display: flex; + align-items: stretch; + border-top: 1px dashed #ccc; + border-bottom: 1px dashed #ccc; + padding: 10rpx 0; + height: 220rpx; /* 固定高度容纳三行 */ +} + +.all, .nearby { + flex-shrink: 0; + width: 15%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 30rpx; + font-weight: bold; +} + +.continents-container { + flex: 1; + display: flex; + flex-direction: column; + justify-content: space-between; +} + +.continent-row { + display: flex; + justify-content: flex-start; /* 改为左对齐 */ + height: 33.33%; /* 每行高度占三分之一 */ + padding: 5rpx 0; +} + +.continent { + flex: 0 0 30%; /* 固定宽度30% */ + display: flex; + justify-content: center; + align-items: center; + text-align: center; + font-size: 28rpx; + padding: 5rpx 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + margin-right: 5%; /* 元素间间距 */ +} + +/* 最后一个元素不需要右边距 */ +.continent:last-child { + margin-right: 0; +} + +.continent-placeholder { + flex: 0 0 30%; /* 占位元素宽度 */ + margin-right: 5%; /* 保持与正常元素相同的间距 */ +} + +/* 最后一个占位元素不需要右边距 */ +.continent-placeholder:last-child { + margin-right: 0; +} + +.separator { + width: 1px; + background-color: #ccc; + margin: 0 10rpx; + align-self: center; + height: 80%; /* 分隔线高度为父容器的80% */ +} + +.nearby { + flex-direction: column; + font-size: 30rpx; +} </style> <style scoped lang="scss"> -- Gitblit v1.9.3