|
|
<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" /> </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="(country, index) in countryList" :key="index" :class="['country_item', selectedCountry === country ? 'selected' : '']" @click="selectCountry(country)"> <text class="country_text">{{ country }}</text> </view> </view> </view> </view> </view> </view></template>
<script setup>import { ref, onMounted, watch, nextTick, computed } from 'vue'import footerBar from './footerBar.vue'import forexMetals from './forexMetals.vue'import marketOverview from './marketOverview.vue'import countryMarket from './countryMarket.vue'
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: '概况' }, { id: 2, title: '新加坡' }, { id: 3, title: '马来西亚' }, { id: 4, title: '印度尼西亚' }, { id: 5, title: '美国' }, { id: 6, title: '中国香港' }, { id: 7, title: '泰国' }, { id: 8, title: '中国' }, { id: 9, title: '加拿大' }, { id: 10, title: '越南' }, { id: 11, title: '外汇' }, { id: 12, 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 countryList = ref([ '概况', '新加坡', '马来西亚', '印度尼西亚', '美国', '中国香港', '泰国', '中国', '加拿大', '越南', '外汇', '贵金属'])
// 搜索输入事件
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(() => { // 状态栏高度
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>
|