4 Commits

Author SHA1 Message Date
49d9bf438e feat: 首页的调整 2026-07-30 14:41:49 +08:00
f17c6e5e3f feat: 调整展示逻辑 2026-07-30 13:40:23 +08:00
9e048a1174 feat: 首页的搭建 2026-07-30 13:14:06 +08:00
dd8e622bad feat: 改版首页搭建 2026-07-30 09:50:59 +08:00
7 changed files with 255 additions and 63 deletions

View File

@@ -138,7 +138,7 @@
</scroll-view> </scroll-view>
<!-- 输入框区域 --> <!-- 输入框区域 -->
<view class="mb-[24px] mt-[8px] mx-[12px] pt-[4px] pb-[8px] shrink-0 rounded-[16px] shadow-[0_0_6px_0_rgba(0,0,0,0.05)]"> <view class="mb-[22px] mt-[6px] mx-[12px] pt-[2px] pb-[8px] shrink-0 rounded-[16px] border border-white shadow-[0_0_6px_0_rgba(0,0,0,0.05)]">
<ChatQuickAccess /> <ChatQuickAccess />
<ChatInputArea ref="inputAreaRef" v-model="inputMessage" :holdKeyboard="holdKeyboard" <ChatInputArea ref="inputAreaRef" v-model="inputMessage" :holdKeyboard="holdKeyboard"
:is-session-active="isSessionActive" :stop-request="stopRequest" @send="sendMessageAction" :is-session-active="isSessionActive" :stop-request="stopRequest" @send="sendMessageAction"

View File

@@ -94,7 +94,7 @@ const clickItem = (item) => {
box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05); box-shadow: 0px 1px 2px 0px rgba(0, 0, 0, 0.05);
border-radius: 8px; border-radius: 8px;
border: 1px solid rgba(255, 255, 255, 0.9); border: 1px solid rgba(255, 255, 255, 0.9);
background: rgba(255, 255, 255, 0.96); background: rgba(255, 255, 255, 0.2);
} }
.notice-footer { .notice-footer {

View File

@@ -18,7 +18,7 @@
> >
<view class="tab-content"> <view class="tab-content">
<view class="tab-label"> <view class="tab-label">
<text class="tab-text">{{ tab.label }}</text> <text class="tab-text">{{ tab.tagName }}</text>
<image v-if="modelValue === idx && (isZhiNian ? indicatorSrcB : indicatorSrc)" :src="isZhiNian ? indicatorSrcB : indicatorSrc" class="tab-indicator" mode="widthFix" /> <image v-if="modelValue === idx && (isZhiNian ? indicatorSrcB : indicatorSrc)" :src="isZhiNian ? indicatorSrcB : indicatorSrc" class="tab-indicator" mode="widthFix" />
</view> </view>
</view> </view>

View File

@@ -0,0 +1,189 @@
<template>
<view class="box-border w-full px-[12px] pb-[20px] pt-[12px]">
<!-- 主景区 -->
<view
v-if="panoramaMainCards.length > 0"
class="relative h-[162px] w-full overflow-hidden rounded-[18px] bg-[#17463d] border border-white"
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] mr-[4px]">开始探索</text>
<uni-icons type="arrow-right" size="16" color="#149474" />
</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]"
>
<view class="mr-[5px] flex items-center">
<uni-icons type="location" size="15" color="#FFFFFF" />
</view>
<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] border border-white"
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-[#149474] bg-[rgba(0,0,0,0.3)]">
<uni-icons type="arrow-right" size="22" color="#FFFFFF" />
</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 h-[130px] min-h-0 overflow-hidden rounded-[18px] bg-[#173a34] border border-white"
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-[46px] w-[46px] items-center justify-center rounded-full border border-white/20 bg-[rgba(0,0,0,0.3)]">
<uni-icons type="arrow-right" size="22" color="#FFFFFF" />
</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,11 +1,25 @@
<template> <template>
<view class="discovery-content"> <view class="flex h-full min-h-0 w-full flex-col bg-[#F0F8F3]">
<view class="discovery-tabs-sticky"> <view
<FindTabs v-if="discoveryTabs.length > 0" v-model="activeIndex" :tabs="discoveryTabs" @change="handleTabChange" /> 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> </view>
<scroll-view class="discovery-scroll" scroll-y @scroll="handleContentScroll"> <scroll-view class="h-0 min-h-0 w-full flex-1 overflow-hidden" scroll-y @scroll="handleContentScroll">
<view class="discovery-body"> <view v-show="isMainPanoramaGuide" class="flex w-full flex-col gap-[12px]">
<PanoramicHome @select="handlePanoramicHomeSelect" />
</view>
<view v-if="!isMainPanoramaGuide" class="flex w-full flex-col gap-[12px]">
<CardSwiper v-if="discoveryCards.length > 0" :list="discoveryCards" @didSelectItem="handleCardClick" /> <CardSwiper v-if="discoveryCards.length > 0" :list="discoveryCards" @didSelectItem="handleCardClick" />
<ExploreCards v-if="exploreCards.length > 0" :list="exploreCards" @didSelectItem="handleCardClick" /> <ExploreCards v-if="exploreCards.length > 0" :list="exploreCards" @didSelectItem="handleCardClick" />
@@ -21,7 +35,7 @@
</template> </template>
<script setup> <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 { SEND_MESSAGE_CONTENT_TEXT, SEND_MESSAGE_COMMAND_TYPE } from "@/constant/constant";
import { navigateTo } from "@/router"; import { navigateTo } from "@/router";
import { getAccessToken } from "@/constant/token"; import { getAccessToken } from "@/constant/token";
@@ -33,6 +47,7 @@ import QuickQuestions from "./components/QuickQuestions/index.vue";
import DiscoveryPhotoToolCard from "./components/DiscoveryPhotoToolCard/index.vue"; import DiscoveryPhotoToolCard from "./components/DiscoveryPhotoToolCard/index.vue";
import DiscoveryAudioToolCard from "./components/DiscoveryAudioToolCard/index.vue"; import DiscoveryAudioToolCard from "./components/DiscoveryAudioToolCard/index.vue";
import DiscoveryGuideMapToolCard from "./components/DiscoveryGuideMapToolCard/index.vue"; import DiscoveryGuideMapToolCard from "./components/DiscoveryGuideMapToolCard/index.vue";
import PanoramicHome from "./components/PanoramicHome/index.vue";
import { homeTabsData, getNearbyTags, homeTabContentData, homeQuickQuestionData } from "../../request/api/MainPageDataApi"; import { homeTabsData, getNearbyTags, homeTabContentData, homeQuickQuestionData } from "../../request/api/MainPageDataApi";
import { useAppStore, useLocationStore } from "@/store"; import { useAppStore, useLocationStore } from "@/store";
@@ -50,16 +65,24 @@ const exploreCards = ref([]);
const discoveryQuickQuestions = ref([]); const discoveryQuickQuestions = ref([]);
const emit = defineEmits(["content-scroll"]); const emit = defineEmits(["content-scroll"]);
const isPanoramaGuideTab = computed(() => { /// 是否是主景区引导页 默认是 true点击主景区后为 false
return discoveryTabs.value[activeIndex.value]?.label === "测试-2.0"; const isMainPanoramaGuide = ref(true);
});
/// 监听页面滚动事件
const handleContentScroll = (e) => { const handleContentScroll = (e) => {
emit("content-scroll", { emit("content-scroll", {
scrollTop: Number(e.detail?.scrollTop || 0), scrollTop: Number(e.detail?.scrollTop || 0),
}); });
}; };
/// 点击主景区
const handleMainPanoramaClick = () => {
isMainPanoramaGuide.value = true;
discoveryCards.value = [];
exploreCards.value = [];
discoveryQuickQuestions.value = [];
};
/// tabs 切换事件 /// tabs 切换事件
const handleTabChange = ({ tab, idx }) => { const handleTabChange = ({ tab, idx }) => {
@@ -68,20 +91,33 @@ const handleTabChange = ({ tab, idx }) => {
} }
/// 请求发现页tab数据 /// 请求发现页tab数据
const queryTabsList = async () => { const queryTabsList = async (homePageId) => {
const res = await homeTabsData(); const res = await homeTabsData({ homePageId });
if (res.code === 0) { if (res.code === 0 && Array.isArray(res.data)) {
/// 处理tab数据 const tabList = res.data;
const tabList = res.data.map((item) => ({ if (tabList.length === 0) {
id: item.id, uni.showToast({
label: item.tagName, title: "暂无数据~",
})); icon: "none"
/// 设置tab数据 });
discoveryTabs.value = tabList; return;
};
isMainPanoramaGuide.value = false;
discoveryTabs.value.push(...tabList);
/// 根据sceneId查询对应tab数据 /// 根据sceneId查询对应tab数据
findTabByIdWithActiveTabIndex(sceneId); findTabByIdWithActiveTabIndex(sceneId);
/// 如果没有sceneId则获取位置信息获取附近标签数据
getLocation();
} }
} }
/// 点击主景区后选择景区后查询对应tab数据
const handlePanoramicHomeSelect = (item) => {
const homePageId = item?.homePageId;
if (!homePageId) return;
queryTabsList(homePageId);
};
/// 根据id查询tab并设置activeIndex /// 根据id查询tab并设置activeIndex
const findTabByIdWithActiveTabIndex = (tabsId) => { const findTabByIdWithActiveTabIndex = (tabsId) => {
/// 查询是否有id参数 /// 查询是否有id参数
@@ -225,43 +261,4 @@ const getNearbyTagsData = async () => {
} }
} }
/// 组件挂载后请求数据
onMounted(() => {
queryTabsList();
getLocation();
});
</script> </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"; import request from "../base/request";
/// 首页全部景区数据
function homeAllScenicData(args) {
return request.post("/hotelBiz/mainScene/queryHomePageListWithCache", args);
}
/// 首页tab场景数据 /// 首页tab场景数据
function homeTabsData(args) { function homeTabsData(args) {
return request.post("/hotelBiz/mainScene/queryTagListWithCache", args); return request.post("/hotelBiz/mainScene/queryTagListWithCache", args);
@@ -47,6 +52,7 @@ function discoveryCradComponent() {
} }
export { export {
homeAllScenicData,
homeTabsData, homeTabsData,
homeTabContentData, homeTabContentData,
getNearbyTags, getNearbyTags,

View File

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