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.

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