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.

749 lines
15 KiB

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