You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

732 lines
14 KiB

4 weeks ago
  1. <template>
  2. <view class="main">
  3. <!-- 可滚动内容区域 -->
  4. <scroll-view class="content_scroll" scroll-y="true" :style="{ top: contentTopPosition + 'px' }">
  5. <view class="content">
  6. <button @click="goToChartExample">图表</button>
  7. <view class="map">
  8. <image src="/static/marketSituation-image/map.png" mode="widthFix"></image>
  9. </view>
  10. <view class="global_index">
  11. <view class="global_index_title">
  12. {{ $t('marketSituation.globalIndex') }}
  13. </view>
  14. <view class="global_index_more" @click="goToGlobalIndex">
  15. <text>{{ $t('marketSituation.globalIndexMore') }}</text>
  16. <image src="/static/marketSituation-image/more.png" mode="aspectFit"></image>
  17. </view>
  18. </view>
  19. <!-- 卡片网格 -->
  20. <view class="cards_grid">
  21. <view v-for="(card, index) in cardData" :key="index" class="card_item">
  22. <IndexCard :flagIcon="card.flagIcon" :indexName="card.indexName"
  23. :currentPrice="card.currentPrice" :changeAmount="card.changeAmount"
  24. :changePercent="card.changePercent" :isRising="card.isRising" />
  25. </view>
  26. </view>
  27. <view class="warn">
  28. <image src="/static/marketSituation-image/warn.png" mode="aspectFit"></image>
  29. <view class="warn_text_container">
  30. <text :class="warnTextClass">{{ $t('marketSituation.warn') }}</text>
  31. </view>
  32. </view>
  33. <!-- 底部安全区域防止被导航栏遮挡 -->
  34. <view class="bottom_safe_area"></view>
  35. </view>
  36. </scroll-view>
  37. </view>
  38. </template>
  39. <script setup>
  40. import { ref, onMounted, watch, nextTick, computed } from 'vue'
  41. import util from '../../common/util.js'
  42. import IndexCard from '../../components/IndexCard.vue'
  43. const iSMT = ref(0)
  44. const searchValue = ref('')
  45. const contentHeight = ref(0)
  46. const headerHeight = ref(0) // 动态计算的header高度
  47. const isWarnTextOverflow = ref(false) // warn文字是否溢出
  48. const pageIndex = ref(0)
  49. const scrollToView = ref('')
  50. // 跳转图表示例页面
  51. const goToChartExample = () => {
  52. uni.navigateTo({
  53. url: '/pages/marketSituation/chartExample'
  54. })
  55. }
  56. // 计算属性:精准计算content区域的top值
  57. const contentTopPosition = computed(() => {
  58. const statusBarHeight = iSMT.value || 0
  59. const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 140
  60. return statusBarHeight + currentHeaderHeight
  61. })
  62. // warn文字的class计算属性
  63. const warnTextClass = computed(() => {
  64. return isWarnTextOverflow.value ? 'warn_text scroll-active' : 'warn_text'
  65. })
  66. // 弹窗相关数据
  67. const showCountryModal = ref(false)
  68. const selectedCountry = ref('概况')
  69. const countryList = ref([
  70. '概况', '新加坡', '马来西亚', '印度尼西亚', '美国', '中国香港',
  71. '泰国', '中国', '加拿大', '越南', '外汇', '贵金属'
  72. ])
  73. // 卡片数据
  74. const cardData = ref([
  75. {
  76. flagIcon: '🇺🇸',
  77. indexName: '道琼斯',
  78. currentPrice: '45757.90',
  79. changeAmount: '-125.22',
  80. changePercent: '-0.27%',
  81. isRising: false
  82. },
  83. {
  84. flagIcon: '🇺🇸',
  85. indexName: '纳斯达克',
  86. currentPrice: '22333.96',
  87. changeAmount: '+125.22',
  88. changePercent: '+0.47%',
  89. isRising: true
  90. },
  91. {
  92. flagIcon: '🇺🇸',
  93. indexName: '标普500',
  94. currentPrice: '6606.08',
  95. changeAmount: '+125.22',
  96. changePercent: '+0.27%',
  97. isRising: true
  98. },
  99. {
  100. flagIcon: '🇨🇳',
  101. indexName: '上证指数',
  102. currentPrice: '3333.96',
  103. changeAmount: '+125.22',
  104. changePercent: '+0.27%',
  105. isRising: true
  106. },
  107. {
  108. flagIcon: '🇨🇳',
  109. indexName: '科创50',
  110. currentPrice: '757.90',
  111. changeAmount: '-25.22',
  112. changePercent: '-0.27%',
  113. isRising: false
  114. },
  115. {
  116. flagIcon: '🇭🇰',
  117. indexName: '恒生指数',
  118. currentPrice: '19757.90',
  119. changeAmount: '-125.22',
  120. changePercent: '-0.63%',
  121. isRising: false
  122. },
  123. {
  124. flagIcon: '🇸🇬',
  125. indexName: '道琼斯',
  126. currentPrice: '3757.90',
  127. changeAmount: '+85.22',
  128. changePercent: '+2.31%',
  129. isRising: true
  130. },
  131. {
  132. flagIcon: '🇲🇾',
  133. indexName: '纳斯达克',
  134. currentPrice: '1657.90',
  135. changeAmount: '-15.22',
  136. changePercent: '-0.91%',
  137. isRising: false
  138. },
  139. {
  140. flagIcon: '🇹🇭',
  141. indexName: '标普500',
  142. currentPrice: '1457.90',
  143. changeAmount: '+35.22',
  144. changePercent: '+2.48%',
  145. isRising: true
  146. }
  147. ])
  148. // 搜索输入事件
  149. const onSearchInput = (e) => {
  150. searchValue.value = e.detail.value
  151. }
  152. // 搜索确认事件
  153. const onSearchConfirm = (e) => {
  154. console.log('搜索内容:', e.detail.value)
  155. // 这里可以添加搜索逻辑
  156. performSearch(e.detail.value)
  157. }
  158. // 搜索图标点击事件
  159. const onSearchClick = () => {
  160. if (searchValue.value.trim()) {
  161. performSearch(searchValue.value)
  162. }
  163. }
  164. // 执行搜索
  165. const performSearch = (keyword) => {
  166. if (!keyword.trim()) {
  167. uni.showToast({
  168. title: '请输入搜索内容',
  169. icon: 'none'
  170. })
  171. return
  172. }
  173. uni.showToast({
  174. title: `搜索: ${keyword}`,
  175. icon: 'none'
  176. })
  177. // 这里添加实际的搜索逻辑
  178. }
  179. // 检测warn文字是否溢出
  180. const checkWarnTextOverflow = () => {
  181. nextTick(() => {
  182. setTimeout(() => {
  183. const query = uni.createSelectorQuery()
  184. // 同时查询容器和文字元素
  185. query.select('.warn_text_container').boundingClientRect()
  186. query.select('.warn_text').boundingClientRect()
  187. query.exec((res) => {
  188. const containerRect = res[0]
  189. const textRect = res[1]
  190. if (!containerRect || !textRect) {
  191. return
  192. }
  193. // 判断文字是否超出容器(留一些余量)
  194. const isOverflow = textRect.width > (containerRect.width - 10)
  195. isWarnTextOverflow.value = isOverflow
  196. })
  197. }, 500)
  198. })
  199. }
  200. // 跳转到全球指数页面
  201. const goToGlobalIndex = () => {
  202. uni.navigateTo({
  203. url: '/pages/marketSituation/globalIndex'
  204. })
  205. }
  206. onMounted(() => {
  207. // 状态栏高度
  208. iSMT.value = uni.getSystemInfoSync().statusBarHeight;
  209. // 确保DOM渲染完成后再查询高度
  210. nextTick(() => {
  211. // 动态计算header实际高度
  212. uni.createSelectorQuery().select('.header_fixed').boundingClientRect((rect) => {
  213. if (rect) {
  214. headerHeight.value = rect.height
  215. console.log('Header实际高度:', headerHeight.value, 'px')
  216. }
  217. }).exec()
  218. // 检测warn文字是否溢出
  219. checkWarnTextOverflow()
  220. })
  221. })
  222. // 监听headerHeight变化,重新计算contentHeight
  223. watch(headerHeight, (newHeight) => {
  224. if (newHeight > 0) {
  225. const systemInfo = uni.getSystemInfoSync()
  226. const windowHeight = systemInfo.windowHeight
  227. const statusBarHeight = systemInfo.statusBarHeight || 0
  228. const footerHeight = 100
  229. contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight
  230. console.log('重新计算contentHeight:', contentHeight.value)
  231. }
  232. })
  233. </script>
  234. <style scoped>
  235. /* 状态栏占位 */
  236. .top {
  237. position: fixed;
  238. top: 0;
  239. left: 0;
  240. right: 0;
  241. z-index: 1001;
  242. background-color: #ffffff;
  243. }
  244. /* 固定头部样式 */
  245. .header_fixed {
  246. position: fixed;
  247. left: 0;
  248. right: 0;
  249. z-index: 1000;
  250. background-color: #ffffff;
  251. padding: 20rpx 0 0 0;
  252. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  253. }
  254. /* 可滚动内容区域 */
  255. .content_scroll {
  256. position: fixed;
  257. left: 0;
  258. right: 0;
  259. bottom: 100rpx;
  260. /* 底部导航栏高度 */
  261. overflow-y: auto;
  262. }
  263. .header_content {
  264. display: flex;
  265. align-items: center;
  266. justify-content: space-between;
  267. height: 80rpx;
  268. padding: 0 20rpx;
  269. margin-bottom: 10rpx;
  270. }
  271. .header_input_wrapper {
  272. display: flex;
  273. align-items: center;
  274. width: 100%;
  275. margin: 0 20rpx 0 0;
  276. height: 70rpx;
  277. border-radius: 35rpx;
  278. background-color: #ffffff;
  279. border: 1rpx solid #e9ecef;
  280. padding: 0 80rpx 0 30rpx;
  281. font-size: 28rpx;
  282. color: #5c5c5c;
  283. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  284. }
  285. .search_icon {
  286. width: 40rpx;
  287. height: 40rpx;
  288. opacity: 0.6;
  289. }
  290. .header_input {
  291. margin-left: 10rpx;
  292. }
  293. .header_icons {
  294. display: flex;
  295. align-items: center;
  296. gap: 15rpx;
  297. }
  298. .header_icon {
  299. width: 40rpx;
  300. height: 40rpx;
  301. display: flex;
  302. align-items: center;
  303. justify-content: center;
  304. }
  305. .header_icon image {
  306. width: 40rpx;
  307. height: 40rpx;
  308. }
  309. /* Tab 栏样式 */
  310. .channel_li {
  311. display: flex;
  312. align-items: center;
  313. height: 80rpx;
  314. background-color: #ffffff;
  315. border-bottom: 1rpx solid #f0f0f0;
  316. }
  317. .channel_wrap {
  318. width: calc(100% - 60rpx);
  319. height: 100%;
  320. overflow: hidden;
  321. flex-shrink: 0;
  322. }
  323. .channel_innerWrap {
  324. display: flex;
  325. align-items: center;
  326. height: 100%;
  327. padding: 0 20rpx;
  328. white-space: nowrap;
  329. }
  330. .channel_item {
  331. position: relative;
  332. display: flex;
  333. flex-direction: column;
  334. align-items: center;
  335. justify-content: center;
  336. height: 60rpx;
  337. padding: 0 20rpx;
  338. border-radius: 30rpx;
  339. transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  340. cursor: pointer;
  341. white-space: nowrap;
  342. flex-shrink: 0;
  343. }
  344. .channel_item:active {
  345. transform: scale(0.98);
  346. }
  347. .channel_item.active {
  348. color: #333;
  349. font-weight: bold;
  350. }
  351. .channel_text {
  352. font-size: 28rpx;
  353. font-weight: 500;
  354. color: #666666;
  355. transition: color 0.3s ease;
  356. white-space: nowrap;
  357. }
  358. .channel_item.active .channel_text {
  359. color: #333333;
  360. font-weight: 400;
  361. z-index: 2;
  362. }
  363. .active_indicator {
  364. position: absolute;
  365. left: 50%;
  366. top: 60%;
  367. transform: translateX(-45%);
  368. width: calc(100% - 20rpx);
  369. min-width: 40rpx;
  370. max-width: 120rpx;
  371. height: 8rpx;
  372. background-image: url('/static/marketSituation-image/bg.png');
  373. background-size: cover;
  374. background-position: center;
  375. background-repeat: no-repeat;
  376. animation: slideIn 0.1s ease;
  377. border-radius: 8rpx;
  378. z-index: 1;
  379. }
  380. @keyframes slideIn {
  381. from {
  382. width: 0;
  383. opacity: 0;
  384. }
  385. to {
  386. width: 40rpx;
  387. opacity: 1;
  388. }
  389. }
  390. .scroll_indicator {
  391. border-left: 1rpx solid #b6b6b6;
  392. display: flex;
  393. align-items: center;
  394. justify-content: center;
  395. width: 60rpx;
  396. height: 30rpx;
  397. background-color: #ffffff;
  398. flex-shrink: 0;
  399. }
  400. .scroll_indicator image {
  401. width: 20rpx;
  402. height: 20rpx;
  403. opacity: 0.5;
  404. }
  405. .content {
  406. margin-top: 20rpx;
  407. background-color: white;
  408. }
  409. .map {
  410. width: calc(100% - 60rpx);
  411. margin: 0 30rpx;
  412. display: flex;
  413. align-items: center;
  414. justify-content: center;
  415. background-color: #F3F3F3;
  416. border-radius: 30rpx;
  417. border: 1rpx solid #E0E0E0;
  418. padding: 30rpx 20rpx;
  419. box-sizing: border-box;
  420. /* 设置最小高度保护,但允许内容撑开 */
  421. min-height: 200rpx;
  422. }
  423. .map image {
  424. width: 100%;
  425. height: auto;
  426. max-width: 100%;
  427. display: block;
  428. /* widthFix模式下,高度会自动按比例调整 */
  429. /* 设置最大高度避免图片过大 */
  430. max-height: 60vh;
  431. /* 添加平滑过渡效果 */
  432. transition: all 0.3s ease;
  433. max-height: 60vh;
  434. }
  435. /* 响应式优化 */
  436. @media screen and (max-width: 750rpx) {
  437. .map {
  438. margin: 0 20rpx;
  439. width: calc(100% - 40rpx);
  440. padding: 20rpx 15rpx;
  441. }
  442. }
  443. @media screen and (max-width: 480rpx) {
  444. .map {
  445. margin: 0 15rpx;
  446. width: calc(100% - 30rpx);
  447. padding: 15rpx 10rpx;
  448. }
  449. }
  450. .static-footer {
  451. position: fixed;
  452. bottom: 0;
  453. }
  454. /* 弹窗样式 */
  455. .modal_overlay {
  456. position: fixed;
  457. top: 0;
  458. left: 0;
  459. right: 0;
  460. bottom: 0;
  461. background-color: rgba(0, 0, 0, 0.5);
  462. display: flex;
  463. align-items: flex-end;
  464. z-index: 1000;
  465. }
  466. .modal_content {
  467. width: 100%;
  468. background-color: #fff;
  469. border-radius: 20rpx 20rpx 0 0;
  470. max-height: 80vh;
  471. overflow: hidden;
  472. }
  473. .modal_header {
  474. position: relative;
  475. display: flex;
  476. justify-content: center;
  477. align-items: center;
  478. padding: 30rpx 40rpx;
  479. border-bottom: 1rpx solid #f0f0f0;
  480. }
  481. .modal_title {
  482. font-size: 32rpx;
  483. font-weight: bold;
  484. color: #333333;
  485. text-align: center;
  486. }
  487. .modal_close {
  488. position: absolute;
  489. right: 40rpx;
  490. top: 50%;
  491. transform: translateY(-50%);
  492. width: 60rpx;
  493. height: 60rpx;
  494. display: flex;
  495. align-items: center;
  496. justify-content: center;
  497. font-size: 40rpx;
  498. color: #999;
  499. }
  500. .modal_body {
  501. padding: 40rpx;
  502. }
  503. .country_grid {
  504. display: grid;
  505. grid-template-columns: 1fr 1fr;
  506. gap: 20rpx;
  507. }
  508. .country_item {
  509. padding: 24rpx 30rpx;
  510. border-radius: 12rpx;
  511. background-color: #f8f8f8;
  512. display: flex;
  513. align-items: center;
  514. justify-content: center;
  515. transition: all 0.3s ease;
  516. }
  517. .country_item.selected {
  518. background-color: #ff4444;
  519. color: #fff;
  520. }
  521. .country_text {
  522. font-size: 28rpx;
  523. color: #333;
  524. }
  525. .country_item.selected .country_text {
  526. color: #fff;
  527. }
  528. .global_index {
  529. margin: 30rpx 20rpx 0 20rpx;
  530. display: flex;
  531. justify-content: space-between;
  532. }
  533. .global_index_title {
  534. margin-left: 20rpx;
  535. font-size: 40rpx;
  536. font-weight: 100;
  537. color: #333333;
  538. align-items: center;
  539. }
  540. .global_index_more {
  541. display: flex;
  542. gap: 10rpx;
  543. font-size: 28rpx;
  544. color: #333333;
  545. align-items: center;
  546. }
  547. .global_index_more image {
  548. width: 40rpx;
  549. height: 40rpx;
  550. align-items: center;
  551. }
  552. /* 卡片网格样式 */
  553. .cards_grid {
  554. display: grid;
  555. grid-template-columns: repeat(3, 1fr);
  556. margin: 0;
  557. box-sizing: border-box;
  558. width: 100%;
  559. padding: 30rpx 20rpx;
  560. gap: 20rpx;
  561. }
  562. .card_item {
  563. width: 100%;
  564. box-sizing: border-box;
  565. min-width: 0;
  566. /* 防止内容溢出 */
  567. }
  568. /* 响应式布局 - 小屏幕时改为两列 */
  569. @media (max-width: 600rpx) {
  570. .cards_grid {
  571. grid-template-columns: repeat(2, 1fr);
  572. padding: 30rpx 20rpx;
  573. }
  574. }
  575. /* 超小屏幕时改为单列 */
  576. @media (max-width: 400rpx) {
  577. .cards_grid {
  578. grid-template-columns: 1fr;
  579. padding: 30rpx 20rpx;
  580. }
  581. }
  582. .warn {
  583. display: flex;
  584. align-items: center;
  585. justify-content: flex-start;
  586. gap: 10rpx;
  587. font-size: 28rpx;
  588. color: #666666;
  589. padding: 20rpx;
  590. max-width: 100%;
  591. overflow: hidden;
  592. position: relative;
  593. }
  594. .warn image {
  595. width: 40rpx;
  596. height: 40rpx;
  597. flex-shrink: 0;
  598. /* 防止图片被压缩 */
  599. position: relative;
  600. z-index: 2;
  601. /* 确保图片在最上层 */
  602. }
  603. .warn_text_container {
  604. flex: 1;
  605. overflow: hidden;
  606. position: relative;
  607. min-width: 0;
  608. /* 允许容器收缩 */
  609. }
  610. .warn_text {
  611. display: block;
  612. white-space: nowrap;
  613. will-change: transform;
  614. /* 优化动画性能 */
  615. }
  616. /* 文字滚动动画 */
  617. @keyframes scrollText {
  618. 0% {
  619. transform: translateX(0);
  620. }
  621. 20% {
  622. transform: translateX(0);
  623. }
  624. 80% {
  625. transform: translateX(-85%);
  626. }
  627. 100% {
  628. transform: translateX(-85%);
  629. }
  630. }
  631. /* 当文字超长时启用滚动动画 */
  632. .warn_text.scroll-active {
  633. animation: scrollText 12s linear infinite;
  634. animation-delay: 2s;
  635. /* 延迟2秒开始滚动,让用户先看到开头 */
  636. }
  637. /* 底部安全区域 */
  638. .bottom_safe_area {
  639. height: 40rpx;
  640. background-color: transparent;
  641. }
  642. /* 主容器样式调整 */
  643. .main {
  644. position: relative;
  645. height: 100vh;
  646. overflow: hidden;
  647. background-color: white;
  648. }
  649. </style>