feat: 首页的搭建

This commit is contained in:
2026-07-30 13:14:06 +08:00
parent dd8e622bad
commit 9e048a1174
4 changed files with 235 additions and 68 deletions

View File

@@ -1,5 +1,187 @@
<template>
<view>
<view class="box-border w-full px-[12px] pb-[20px] pt-[12px]">
<!-- 主景区 -->
<view
v-if="panoramaMainCards.length > 0"
class="relative h-[158px] w-full overflow-hidden rounded-[18px] bg-[#17463d]"
hover-class="opacity-90"
hover-stay-time="80"
@tap="handleCardTap(panoramaMainCards[0])"
>
<image
class="absolute inset-0 h-full w-full"
:src="panoramaMainCards[0].coverImageUrl"
mode="aspectFill"
/>
<view class="absolute inset-0 bg-[linear-gradient(90deg,rgba(3,39,33,0.78)_0%,rgba(3,39,33,0.34)_55%,rgba(3,39,33,0.08)_100%)]" />
<view class="absolute inset-0 box-border flex flex-col items-start justify-center px-[20px] py-[16px]">
<text class="block text-[27px] font-bold leading-[1.15] tracking-[1px] text-white">
{{ panoramaMainCards[0].title }}
</text>
<text class="mt-[7px] block text-[12px] leading-[1.4] tracking-[0.5px] text-white/90">
{{ panoramaMainCards[0].subtitle }}
</text>
<view class="mt-[15px] flex h-[36px] items-center rounded-full bg-white/95 px-[16px]">
<text class="text-[14px] font-semibold text-[#149474]">开始探索</text>
<text class="ml-[9px] text-[22px] leading-none text-[#149474]"></text>
</view>
</view>
<view
v-if="panoramaMainCards[0].locationName"
class="absolute bottom-[14px] right-[14px] flex h-[28px] items-center rounded-full border border-white/35 bg-black/20 px-[10px]"
>
<text class="mr-[5px] text-[13px] leading-none text-white"></text>
<text class="text-[11px] text-white/90">{{ panoramaMainCards[0].locationName }}</text>
</view>
</view>
<!-- 其他景区 -->
<view
v-if="panoramaOtherCards.length > 0 || fineFoodCards.length > 0"
class="mt-[12px] grid grid-cols-12 gap-[10px]"
>
<view
v-if="panoramaOtherCards.length > 0"
class="relative col-span-6 row-span-2 min-h-0 overflow-hidden rounded-[18px] bg-[#173a34]"
hover-class="opacity-90"
hover-stay-time="80"
@tap="handleCardTap(activeOtherCard)"
>
<swiper
class="h-full w-full"
circular
autoplay
:interval="3500"
:duration="500"
@change="handleSwiperChange"
>
<swiper-item
v-for="(card, cardIndex) in panoramaOtherCards"
:key="card.homePageId || cardIndex"
>
<image class="h-full w-full" :src="card.coverImageUrl" mode="aspectFill" />
</swiper-item>
</swiper>
<view class="pointer-events-none absolute inset-0 bg-[linear-gradient(180deg,rgba(5,24,24,0.72)_0%,rgba(5,24,24,0.18)_46%,rgba(5,24,24,0.34)_100%)]" />
<view
v-if="activeOtherCard"
class="pointer-events-none absolute left-[16px] right-[14px] top-[17px]"
>
<text class="block text-[21px] font-bold leading-[1.2] tracking-[0.5px] text-white">
{{ activeOtherCard.title }}
</text>
<text class="mt-[7px] block text-[11px] leading-[1.45] tracking-[0.2px] text-white/85">
{{ activeOtherCard.subtitle }}
</text>
</view>
<view class="pointer-events-none absolute bottom-[16px] left-[16px] flex h-[46px] w-[46px] items-center justify-center rounded-full border border-white/20 bg-[#25a98f]/85">
<text class="text-[27px] font-light leading-none text-white"></text>
</view>
<view
v-if="panoramaOtherCards.length > 1"
class="pointer-events-none absolute bottom-[20px] right-[14px] flex items-center gap-[5px]"
>
<view
v-for="(card, dotIndex) in panoramaOtherCards"
:key="`${card.homePageId || dotIndex}-dot`"
class="h-[5px] rounded-full transition-all duration-300"
:class="dotIndex === activeSwiperIndex ? 'w-[14px] bg-white' : 'w-[5px] bg-white/45'"
/>
</view>
</view>
<!-- 美食 -->
<view
v-for="(card, cardIndex) in fineFoodCards"
:key="card.homePageId || cardIndex"
class="relative col-span-6 aspect-video min-h-0 overflow-hidden rounded-[18px] bg-[#173a34]"
hover-class="opacity-90"
hover-stay-time="80"
@tap="handleCardTap(card)"
>
<image class="h-full w-full" :src="card.coverImageUrl" mode="aspectFill" />
<view class="pointer-events-none absolute inset-0 bg-[linear-gradient(180deg,rgba(5,24,24,0.72)_0%,rgba(5,24,24,0.18)_46%,rgba(5,24,24,0.34)_100%)]" />
<view class="pointer-events-none absolute left-[12px] right-[10px] top-[11px]">
<text class="block text-[17px] font-bold leading-[1.2] tracking-[0.5px] text-white">
{{ card.title }}
</text>
<text class="mt-[3px] block text-[9px] leading-[1.45] tracking-[0.2px] text-white/85">
{{ card.subtitle }}
</text>
</view>
<view class="pointer-events-none absolute bottom-[10px] left-[12px] flex h-[30px] w-[30px] items-center justify-center rounded-full border border-white/20 bg-[#9b7545]/80">
<text class="text-[20px] font-light leading-none text-white"></text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { computed, onMounted, ref } from "vue";
import { homeAllScenicData } from "@/request/api/MainPageDataApi";
const emit = defineEmits(["select"]);
const panoramaMainCards = ref([]);
const panoramaOtherCards = ref([]);
const fineFoodCards = ref([]);
const activeSwiperIndex = ref(0);
const activeOtherCard = computed(() => {
if (panoramaOtherCards.value.length === 0) return null;
return panoramaOtherCards.value[activeSwiperIndex.value] || panoramaOtherCards.value[0];
});
const handleSwiperChange = (event) => {
activeSwiperIndex.value = Number(event.detail?.current || 0);
};
const resetScenicCards = () => {
panoramaMainCards.value = [];
panoramaOtherCards.value = [];
fineFoodCards.value = [];
activeSwiperIndex.value = 0;
};
const getHomeAllScenicData = async () => {
try {
const res = await homeAllScenicData();
if (res?.code === 0 && Array.isArray(res.data)) {
const scenicCards = res.data.filter(
(item) => item && typeof item === "object"
);
const fineFoodStartIndex = Math.max(1, scenicCards.length - 2);
panoramaMainCards.value = scenicCards.slice(0, 1);
panoramaOtherCards.value = scenicCards.slice(1, fineFoodStartIndex);
fineFoodCards.value = scenicCards.slice(fineFoodStartIndex);
activeSwiperIndex.value = 0;
return;
}
resetScenicCards();
console.error("获取景区数据失败", res);
} catch (error) {
resetScenicCards();
console.error("获取景区数据异常", error);
}
};
const handleCardTap = (card) => {
emit("select", card);
};
onMounted(() => {
getHomeAllScenicData();
});
</script>

View File

@@ -1,14 +1,25 @@
<template>
<view class="discovery-content">
<view v-if="!isPanoramaGuideTab" class="discovery-tabs-sticky">
<FindTabs v-model="activeIndex" :tabs="discoveryTabs" @change="handleTabChange" />
<view class="flex h-full min-h-0 w-full flex-col bg-[#F0F8F3]">
<view
v-if="!isMainPanoramaGuide"
class="z-[12] flex w-full shrink-0 flex-row items-center bg-[#F0F8F3]"
>
<view
class="ml-[12px] shrink-0 whitespace-nowrap px-[4px] text-[16px] font-medium text-[rgba(128,140,153,0.9)]"
@click="handleMainPanoramaClick"
>
荔波全景
</view>
<view v-if="discoveryTabs.length > 0" class="min-w-0 flex-1 overflow-hidden">
<FindTabs v-model="activeIndex" :tabs="discoveryTabs" @change="handleTabChange" />
</view>
</view>
<scroll-view class="discovery-scroll" scroll-y @scroll="handleContentScroll">
<view v-if="isPanoramaGuideTab" class="discovery-body">
<PanoramicHome />
<scroll-view class="h-0 min-h-0 w-full flex-1 overflow-hidden" scroll-y @scroll="handleContentScroll">
<view v-if="isMainPanoramaGuide" class="flex w-full flex-col gap-[12px]">
<PanoramicHome @select="handlePanoramicHomeSelect" />
</view>
<view v-else class="discovery-body">
<view v-else class="flex w-full flex-col gap-[12px]">
<CardSwiper v-if="discoveryCards.length > 0" :list="discoveryCards" @didSelectItem="handleCardClick" />
<ExploreCards v-if="exploreCards.length > 0" :list="exploreCards" @didSelectItem="handleCardClick" />
@@ -24,7 +35,7 @@
</template>
<script setup>
import { computed, onMounted, ref } from "vue";
import { computed, ref } from "vue";
import { SEND_MESSAGE_CONTENT_TEXT, SEND_MESSAGE_COMMAND_TYPE } from "@/constant/constant";
import { navigateTo } from "@/router";
import { getAccessToken } from "@/constant/token";
@@ -48,18 +59,14 @@ const locationStore = useLocationStore();
const sceneId = appStore.sceneId || "";
const activeIndex = ref(0);
const discoveryTabs = ref([{
id: '99999',
tagName: "荔波全景",
}]);
const discoveryTabs = ref([]);
const discoveryCards = ref([]);
const exploreCards = ref([]);
const discoveryQuickQuestions = ref([]);
const emit = defineEmits(["content-scroll"]);
const isPanoramaGuideTab = computed(() => {
return discoveryTabs.value[activeIndex.value]?.tagName === "荔波全景";
});
/// 是否是主景区引导页 默认是 true点击主景区后为 false
const isMainPanoramaGuide = ref(true);
const handleContentScroll = (e) => {
emit("content-scroll", {
@@ -67,22 +74,24 @@ const handleContentScroll = (e) => {
});
};
/// 点击主景区
const handleMainPanoramaClick = () => {
isMainPanoramaGuide.value = true;
discoveryCards.value = [];
exploreCards.value = [];
discoveryQuickQuestions.value = [];
};
/// tabs 切换事件
const handleTabChange = ({ tab, idx }) => {
activeIndex.value = idx;
if (isPanoramaGuideTab.value) {
discoveryCards.value = [];
exploreCards.value = [];
discoveryQuickQuestions.value = [];
return;
}
queryDiscoveryData(tab.id);
}
/// 请求发现页tab数据
const queryTabsList = async () => {
const res = await homeTabsData();
const queryTabsList = async (homePageId) => {
const res = await homeTabsData({ homePageId });
if (res.code === 0) {
/// 处理tab数据
const tabList = res.data;
@@ -92,9 +101,18 @@ const queryTabsList = async () => {
findTabByIdWithActiveTabIndex(sceneId);
}
}
const handlePanoramicHomeSelect = (item) => {
const homePageId = item?.homePageId;
if (!homePageId) return;
isMainPanoramaGuide.value = false;
queryTabsList(homePageId);
getLocation();
};
/// 根据id查询tab并设置activeIndex
const findTabByIdWithActiveTabIndex = (tabsId) => {
if (isPanoramaGuideTab.value) return;
/// 查询是否有id参数
const activeTabIndex = discoveryTabs.value.findIndex((tab) => tab.id === tabsId);
/// 如果有则优先展示对应tab数据没有则展示第一个tab数据
@@ -236,43 +254,4 @@ const getNearbyTagsData = async () => {
}
}
/// 组件挂载后请求数据
onMounted(() => {
queryTabsList();
getLocation();
});
</script>
<style lang="scss" scoped>
.discovery-content {
display: flex;
flex-direction: column;
width: 100%;
height: 100%;
min-height: 0;
background: #F0F8F3;
}
.discovery-tabs-sticky {
flex-shrink: 0;
z-index: 12;
width: 100%;
background: #F0F8F3;
}
.discovery-scroll {
flex: 1;
width: 100%;
height: 0;
min-height: 0;
overflow: hidden;
}
.discovery-body {
width: 100%;
gap: 12px;
display: flex;
flex-direction: column;
}
</style>

View File

@@ -1,5 +1,10 @@
import request from "../base/request";
/// 首页全部景区数据
function homeAllScenicData(args) {
return request.post("/hotelBiz/mainScene/queryHomePageListWithCache", args);
}
/// 首页tab场景数据
function homeTabsData(args) {
return request.post("/hotelBiz/mainScene/queryTagListWithCache", args);
@@ -47,6 +52,7 @@ function discoveryCradComponent() {
}
export {
homeAllScenicData,
homeTabsData,
homeTabContentData,
getNearbyTags,

View File

@@ -6,8 +6,8 @@ import { getServiceUrl } from "../api/GetServiceUrlApi";
/// 版本号, 每次发版本前增加
const versionValue = "1.1.3";
/// 是否是测试版本, 测试版本为true 发布版本为false
const developVersion = false;
/// 是否是测试版本, 测试版本为 true 发布版本为 false
const developVersion = true;
// 获取服务地址
const getEvnUrl = async () => {