|
|
<!-- @format -->
<template> <view> <view class="main"> <!-- 固定头部 --> <view class="header_fixed" :style="{ top: iSMT + 'px' }"> <view class="header_content"> <view class="header_input_wrapper"> <image class="search_icon" src="/static/marketSituation-image/search.png" mode="" @click="onSearchClick"></image> <input class="header_input" type="text" placeholder="搜索" placeholder-style="color: #A6A6A6; font-size: 22rpx;" v-model="searchValue" @input="onSearchInput" @confirm="onSearchConfirm" /> </view> <view class="header_icons"> <view class="header_icon" @click="selected"> <image src="/static/marketSituation-image/mySeclected.png" mode=""></image> </view> <view class="header_icon" @click="history"> <image src="/static/marketSituation-image/history.png" mode=""></image> </view> </view> </view> <view class="channel_li" v-if="channelData.length > 0"> <scroll-view class="channel_wrap" scroll-x="true" :scroll-into-view="scrollToView" :scroll-with-animation="true" show-scrollbar="false"> <view class="channel_innerWrap"> <view v-for="(item, index) in channelData" :key="item.id" :id="'nav' + item.id" :class="['channel_item', index === pageIndex ? 'active' : '']" @click="navClick(index)"> <text class="channel_text">{{ item.title }}</text> <view v-if="index === pageIndex" class="active_indicator"></view> </view> </view> </scroll-view> <view class="scroll_indicator" @click="channel_more"> <image src="/static/marketSituation-image/menu.png" mode="aspectFit"></image> </view> </view> </view>
<!-- 可滚动内容区域 --> <scroll-view class="content_scroll" scroll-y="true" :style="{ top: contentTopPosition + 'px' }"> <!-- 动态组件切换 --> <component :is="currentComponent" :countryId="currentChannelId" :marketTabs="getChildMarketTabs(currentChannelId)" /> </scroll-view> </view>
<footerBar class="static-footer" :type="type"></footerBar>
<!-- 更多tab弹窗 --> <view v-if="showCountryModal" class="modal_overlay" @click="closeModal"> <view class="modal_content" @click.stop> <view class="modal_header"> <text class="modal_title">全部栏目</text> <view class="modal_close" @click="closeModal"> <text>×</text> </view> </view> <view class="modal_body"> <view class="country_grid"> <view v-for="(item, index) in channelData" :key="index" :class="['country_item', selectedCountry === item.title ? 'selected' : '']" @click="selectCountry(item.title)"> <text class="country_text">{{ item.title }}</text> </view> </view> </view> </view> </view> </view></template>
<script setup>import { ref, onMounted, watch, nextTick, computed } from "vue";import footerBar from "../../components/footerBar.vue";import forexMetals from "./forexMetals.vue";import marketOverview from "./marketOverview.vue";import countryMarket from "./countryMarket.vue";import { getAllTabsAPI } from "../../api/marketSituation/marketSituation.js";const type = ref("marketSituation");const iSMT = ref(0);const searchValue = ref("");const contentHeight = ref(0);const headerHeight = ref(0); // 动态计算的header高度
// Tab 栏相关数据
const channelData = ref([{ id: 1, title: "概况" }]);const pageIndex = ref(0);const scrollToView = ref("");
// 动态组件相关
const currentChannelId = computed(() => { return channelData.value[pageIndex.value]?.id || 1;});
const currentComponent = computed(() => { const channelId = currentChannelId.value;
// 概况页面使用 MarketOverview 组件
if (pageIndex.value === 0) { return marketOverview; } // 外汇(id=11)和贵金属(id=12)使用 ForexMetals 组件
else if (channelId === 11 || channelId === 12) { return forexMetals; } // 其他国家/地区页面使用 CountryMarket 组件
else { return countryMarket; }});
// 计算属性:精准计算content区域的top值
const contentTopPosition = computed(() => { const statusBarHeight = iSMT.value || 0; const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 140; return statusBarHeight + currentHeaderHeight;});
// 弹窗相关数据
const showCountryModal = ref(false);const selectedCountry = ref("概况");
const getAllTabs = async () => { try { const result = await getAllTabsAPI(); channelData.value = result.data.map((item) => ({ ...item, id: item.id, title: item.tradeName })); } catch (e) { console.error("获取地区分组列表失败", e); }};
const getChildMarketTabs = (id) => { return channelData.value.filter((item) => item.id === id)[0]?.children?.map((child) => child.tradeName);};
// 搜索输入事件
const onSearchInput = (e) => { searchValue.value = e.detail.value;};
// 搜索确认事件
const onSearchConfirm = (e) => { console.log("搜索内容:", e.detail.value); // 这里可以添加搜索逻辑
performSearch(e.detail.value);};
// 搜索图标点击事件
const onSearchClick = () => { if (searchValue.value.trim()) { performSearch(searchValue.value); }};
// 执行搜索
const performSearch = (keyword) => { if (!keyword.trim()) { uni.showToast({ title: "请输入搜索内容", icon: "none", }); return; }
uni.showToast({ title: `搜索: ${keyword}`, icon: "none", }); // 这里添加实际的搜索逻辑
};
// 我的收藏点击事件
const selected = () => { uni.showToast({ title: "我的收藏", icon: "none", }); // 这里可以跳转到收藏页面
};
// 历史记录点击事件
const history = () => { uni.showToast({ title: "历史记录", icon: "none", }); // 这里可以跳转到历史页面
};
// Tab 栏点击事件
const navClick = (index) => { pageIndex.value = index; const currentItem = channelData.value[index]; scrollToView.value = "nav" + currentItem.id;
// 同步更新弹窗中的选中状态
selectedCountry.value = currentItem.title;
uni.showToast({ title: `切换到: ${currentItem.title}`, icon: "none", });
// 这里可以添加切换 tab 后的数据加载逻辑
console.log("当前选中的 tab:", currentItem);};
// 更多选项点击事件
const channel_more = () => { showCountryModal.value = true;};
// 选择国家
const selectCountry = (country) => { selectedCountry.value = country;
// 查找对应的tab索引
const targetIndex = channelData.value.findIndex((item) => item.title === country);
if (targetIndex !== -1) { // 同步更新页面tab
pageIndex.value = targetIndex; const currentItem = channelData.value[targetIndex]; scrollToView.value = "nav" + currentItem.id;
console.log("选中了:" + country + ",同步到tab索引:" + targetIndex); uni.showToast({ title: "已切换到:" + country, icon: "none", duration: 2000, }); } else { // 如果是"概况"或其他特殊选项,默认切换到第一个tab
if (country === "概况" || country === "全部") { pageIndex.value = 0; scrollToView.value = "nav" + channelData.value[0].id; }
console.log("选中了:" + country); uni.showToast({ title: "已选择:" + country, icon: "none", duration: 2000, }); }
// 这里可以添加切换到对应国家/地区数据的逻辑
// 例如:loadMarketData(country)
closeModal();};
// 关闭弹窗
const closeModal = () => { showCountryModal.value = false;};
onMounted(async () => { await getAllTabs(); // 状态栏高度
iSMT.value = uni.getSystemInfoSync().statusBarHeight;
// 初始化 tab 栏
if (channelData.value.length > 0) { pageIndex.value = 0; scrollToView.value = "nav" + channelData.value[0].id; }
// 确保DOM渲染完成后再查询高度
nextTick(() => { // 动态计算header实际高度
uni .createSelectorQuery() .select(".header_fixed") .boundingClientRect((rect) => { if (rect) { headerHeight.value = rect.height; console.log("Header实际高度:", headerHeight.value, "px"); } }) .exec(); });});
// 监听headerHeight变化,重新计算contentHeight
watch(headerHeight, (newHeight) => { if (newHeight > 0) { const systemInfo = uni.getSystemInfoSync(); const windowHeight = systemInfo.windowHeight; const statusBarHeight = systemInfo.statusBarHeight || 0; const footerHeight = 100;
contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight; console.log("重新计算contentHeight:", contentHeight.value); }});</script>
<style scoped>/* 主容器样式调整 */.main { position: relative; height: 100vh; overflow: hidden; background-color: white;}
/* 状态栏占位 */.top { position: fixed; top: 0; left: 0; right: 0; z-index: 1001; background-color: #ffffff;}
/* 固定头部样式 */.header_fixed { position: fixed; left: 0; right: 0; z-index: 1000; background-color: #ffffff; padding: 20rpx 0 0 0; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);}
/* 可滚动内容区域 */.content_scroll { position: fixed; left: 0; right: 0; bottom: 100rpx; /* 底部导航栏高度 */ overflow-y: auto;}
.header_content { display: flex; align-items: center; justify-content: space-between; height: 80rpx; padding: 0 20rpx; margin-bottom: 10rpx;}
.header_input_wrapper { display: flex; align-items: center; width: 100%; margin: 0 20rpx 0 0; height: 70rpx; border-radius: 35rpx; background-color: #ffffff; border: 1rpx solid #e9ecef; padding: 0 80rpx 0 30rpx; font-size: 28rpx; color: #5c5c5c; box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);}
.search_icon { width: 40rpx; height: 40rpx; opacity: 0.6;}
.header_input { margin-left: 10rpx;}
.header_icons { display: flex; align-items: center; gap: 15rpx;}
.header_icon { width: 40rpx; height: 40rpx; display: flex; align-items: center; justify-content: center;}
.header_icon image { width: 40rpx; height: 40rpx;}
/* Tab 栏样式 */.channel_li { display: flex; align-items: center; height: 80rpx; background-color: #ffffff; border-bottom: 1rpx solid #f0f0f0;}
.channel_wrap { width: calc(100% - 60rpx); height: 100%; overflow: hidden; flex-shrink: 0;}
.channel_innerWrap { display: flex; align-items: center; height: 100%; padding: 0 20rpx; white-space: nowrap;}
.channel_item { position: relative; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 60rpx; padding: 0 20rpx; border-radius: 30rpx; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; white-space: nowrap; flex-shrink: 0;}
.channel_item:active { transform: scale(0.98);}
.channel_item.active { color: #333; font-weight: bold;}
.channel_text { font-size: 28rpx; font-weight: 500; color: #666666; transition: color 0.3s ease; white-space: nowrap;}
.channel_item.active .channel_text { color: #333333; font-weight: 400; z-index: 2;}
.active_indicator { position: absolute; left: 50%; top: 60%; transform: translateX(-45%); width: calc(100% - 20rpx); min-width: 40rpx; max-width: 120rpx; height: 8rpx; background-image: url("/static/marketSituation-image/bg.png"); background-size: cover; background-position: center; background-repeat: no-repeat; animation: slideIn 0.1s ease; border-radius: 8rpx; z-index: 1;}
@keyframes slideIn { from { width: 0; opacity: 0; }
to { width: 40rpx; opacity: 1; }}
.scroll_indicator { border-left: 1rpx solid #b6b6b6; display: flex; align-items: center; justify-content: center; width: 60rpx; height: 30rpx; background-color: #ffffff; flex-shrink: 0;}
.scroll_indicator image { width: 20rpx; height: 20rpx; opacity: 0.5;}
.content { margin-top: 20rpx; background-color: white;}
.static-footer { position: fixed; bottom: 0;}
/* 弹窗样式 */.modal_overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); display: flex; align-items: flex-end; z-index: 1000;}
.modal_content { width: 100%; background-color: #fff; border-radius: 20rpx 20rpx 0 0; max-height: 80vh; overflow: hidden;}
.modal_header { position: relative; display: flex; justify-content: center; align-items: center; padding: 30rpx 40rpx; border-bottom: 1rpx solid #f0f0f0;}
.modal_title { font-size: 32rpx; font-weight: bold; color: #333333; text-align: center;}
.modal_close { position: absolute; right: 40rpx; top: 50%; transform: translateY(-50%); width: 60rpx; height: 60rpx; display: flex; align-items: center; justify-content: center; font-size: 40rpx; color: #999;}
.modal_body { padding: 40rpx;}
.country_grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20rpx;}
.country_item { padding: 24rpx 30rpx; border-radius: 12rpx; background-color: #f8f8f8; display: flex; align-items: center; justify-content: center; transition: all 0.3s ease;}
.country_item.selected { background-color: #ff4444; color: #fff;}
.country_text { font-size: 28rpx; color: #333;}
.country_item.selected .country_text { color: #fff;}</style>
|