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.

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