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.

579 lines
14 KiB

  1. <!-- @format -->
  2. <template>
  3. <view class="main">
  4. <!-- 固定头部 -->
  5. <view class="header_fixed" :style="{ top: iSMT + 'px' }">
  6. <view class="header_content">
  7. <view class="header_back" @click="goBack">
  8. <image src="/static/marketSituation-image/back.png" mode=""></image>
  9. </view>
  10. <view class="header_input_wrapper">
  11. <image class="search_icon" src="/static/marketSituation-image/search.png" mode="" @click="onSearchClick"></image>
  12. <input class="header_input" type="text" placeholder="搜索" placeholder-style="color: #A6A6A6; font-size: 22rpx;" v-model="searchValue" @input="onSearchInput" @confirm="onSearchConfirm" />
  13. </view>
  14. <view class="header_icons">
  15. <view class="header_icon" @click="selected">
  16. <image src="/static/marketSituation-image/mySeclected.png" mode=""></image>
  17. </view>
  18. <view class="header_icon" @click="history">
  19. <image src="/static/marketSituation-image/history.png" mode=""></image>
  20. </view>
  21. </view>
  22. </view>
  23. <view class="warn">
  24. <image src="/static/marketSituation-image/warn.png" mode="aspectFit"></image>
  25. <view class="warn_text_container">
  26. <text :class="warnTextClass">{{ $t("marketSituation.warn") }}</text>
  27. </view>
  28. </view>
  29. </view>
  30. <!-- 内容区域 -->
  31. <scroll-view class="content" :style="{ top: contentTopPosition + 'px' }" scroll-y="true">
  32. <!-- 亚太-中华 -->
  33. <view class="market-section">
  34. <view class="market-header">
  35. <text class="market-title">亚太-中华</text>
  36. <view class="market-more" @click="viewMore('asia-china')">
  37. <text class="more-text">查看更多</text>
  38. <text class="more-arrow">></text>
  39. </view>
  40. </view>
  41. <view class="cards-grid-three">
  42. <view v-for="(item, index) in asiachinaIndexes" :key="index" class="card-item">
  43. <IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :changePercent="item.changePercent" :isRising="item.isRising" @click="viewIndexDetail(item)" />
  44. </view>
  45. </view>
  46. </view>
  47. <!-- 亚太 -->
  48. <view class="market-section">
  49. <view class="market-header">
  50. <text class="market-title">亚太</text>
  51. <view class="market-more" @click="viewMore('asia')">
  52. <text class="more-text">查看更多</text>
  53. <text class="more-arrow">></text>
  54. </view>
  55. </view>
  56. <view class="cards-grid-three">
  57. <view v-for="(item, index) in asiaIndexes" :key="index" class="card-item">
  58. <IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :changePercent="item.changePercent" :isRising="item.isRising" @click="viewIndexDetail(item)" />
  59. </view>
  60. </view>
  61. </view>
  62. <!-- 美洲 -->
  63. <view class="market-section">
  64. <view class="market-header">
  65. <text class="market-title">美洲</text>
  66. <view class="market-more" @click="viewMore('america')">
  67. <text class="more-text">查看更多</text>
  68. <text class="more-arrow">></text>
  69. </view>
  70. </view>
  71. <view class="cards-grid-three">
  72. <view v-for="(item, index) in americaIndexes" :key="index" class="card-item">
  73. <IndexCard :flagIcon="item.flagIcon" :stockName="item.stockName" :currentPrice="item.currentPrice" :changeAmount="item.changeAmount" :changePercent="item.changePercent" :isRising="item.isRising" @click="viewIndexDetail(item)" />
  74. </view>
  75. </view>
  76. </view>
  77. <!-- 底部安全区域 -->
  78. <view class="bottom-safe-area"></view>
  79. </scroll-view>
  80. </view>
  81. <!-- 底部导航栏 -->
  82. <footerBar class="static-footer" :type="'marketSituation'"></footerBar>
  83. </template>
  84. <script setup>
  85. import { ref, onMounted, computed, nextTick, watch } from "vue";
  86. import footerBar from "../../components/footerBar.vue";
  87. import IndexCard from "../../components/IndexCard.vue";
  88. // 响应式数据
  89. const iSMT = ref(0); // 状态栏高度
  90. const contentHeight = ref(0);
  91. const headerHeight = ref(0); // 头部高度
  92. const searchValue = ref(""); // 搜索值
  93. const isWarnTextOverflow = ref(false); // warn文字是否溢出
  94. // warn文字的class计算属性
  95. const warnTextClass = computed(() => {
  96. return isWarnTextOverflow.value ? "warn_text scroll-active" : "warn_text";
  97. });
  98. // 检测warn文字是否溢出
  99. const checkWarnTextOverflow = () => {
  100. nextTick(() => {
  101. setTimeout(() => {
  102. const query = uni.createSelectorQuery();
  103. // 同时查询容器和文字元素
  104. query.select(".warn_text_container").boundingClientRect();
  105. query.select(".warn_text").boundingClientRect();
  106. query.exec((res) => {
  107. const containerRect = res[0];
  108. const textRect = res[1];
  109. if (!containerRect || !textRect) {
  110. return;
  111. }
  112. // 判断文字是否超出容器(留一些余量)
  113. const isOverflow = textRect.width > containerRect.width - 10;
  114. isWarnTextOverflow.value = isOverflow;
  115. });
  116. }, 500);
  117. });
  118. };
  119. // 亚太-中华指数数据
  120. const asiachinaIndexes = ref([
  121. {
  122. flagIcon: "/static/c1.png",
  123. stockName: "上证指数",
  124. stockCode: "1A0001",
  125. currentPrice: "3933.96",
  126. changeAmount: "+24.32",
  127. changePercent: "+0.62%",
  128. isRising: true,
  129. },
  130. {
  131. flagIcon: "/static/c2.png",
  132. stockName: "深证成指",
  133. stockCode: "2A01",
  134. currentPrice: "45757.90",
  135. changeAmount: "-123.45",
  136. changePercent: "-0.27%",
  137. isRising: false,
  138. },
  139. {
  140. flagIcon: "/static/c3.png",
  141. stockName: "创业板指",
  142. stockCode: "399006",
  143. currentPrice: "6606.08",
  144. changeAmount: "+89.76",
  145. changePercent: "+1.38%",
  146. isRising: true,
  147. },
  148. {
  149. flagIcon: "/static/c4.png",
  150. stockName: "沪深300",
  151. stockCode: "1B0300",
  152. currentPrice: "45757.90",
  153. changeAmount: "-89.12",
  154. changePercent: "-0.19%",
  155. isRising: false,
  156. },
  157. {
  158. flagIcon: "/static/c5.png",
  159. stockName: "上证50",
  160. stockCode: "1B0011",
  161. currentPrice: "45757.90",
  162. changeAmount: "+234.56",
  163. changePercent: "+0.52%",
  164. isRising: true,
  165. },
  166. {
  167. flagIcon: "/static/c6.png",
  168. stockName: "科创50",
  169. stockCode: "1B0688",
  170. currentPrice: "22333.96",
  171. changeAmount: "+156.78",
  172. changePercent: "+0.71%",
  173. isRising: true,
  174. },
  175. ]);
  176. // 亚太指数数据
  177. const asiaIndexes = ref([
  178. {
  179. flagIcon: "/static/c7.png",
  180. stockName: "日经225",
  181. stockCode: "noCode",
  182. currentPrice: "28456.78",
  183. changeAmount: "+234.56",
  184. changePercent: "+0.83%",
  185. isRising: true,
  186. },
  187. {
  188. flagIcon: "/static/c8.png",
  189. stockName: "韩国KOSPI",
  190. stockCode: "noCode",
  191. currentPrice: "2567.89",
  192. changeAmount: "-12.34",
  193. changePercent: "-0.48%",
  194. isRising: false,
  195. },
  196. {
  197. flagIcon: "/static/c9.png",
  198. stockName: "印度孟买",
  199. stockCode: "noCode",
  200. currentPrice: "65432.10",
  201. changeAmount: "+456.78",
  202. changePercent: "+0.70%",
  203. isRising: true,
  204. },
  205. ]);
  206. // 美洲指数数据
  207. const americaIndexes = ref([
  208. {
  209. flagIcon: "/static/c7.png",
  210. stockName: "道琼斯指数",
  211. stockCode: "noCode",
  212. currentPrice: "34567.89",
  213. changeAmount: "+123.45",
  214. changePercent: "+0.36%",
  215. isRising: true,
  216. },
  217. {
  218. flagIcon: "/static/c8.png",
  219. stockName: "纳斯达克",
  220. stockCode: "noCode",
  221. currentPrice: "13456.78",
  222. changeAmount: "-67.89",
  223. changePercent: "-0.50%",
  224. isRising: false,
  225. },
  226. {
  227. flagIcon: "/static/c9.png",
  228. stockName: "标普500",
  229. stockCode: "noCode",
  230. currentPrice: "4234.56",
  231. changeAmount: "+23.45",
  232. changePercent: "+0.56%",
  233. isRising: true,
  234. },
  235. ]);
  236. // 计算属性:内容区域顶部位置
  237. const contentTopPosition = computed(() => {
  238. const statusBarHeight = iSMT.value || 0;
  239. const currentHeaderHeight = headerHeight.value > 0 ? headerHeight.value : 100;
  240. return statusBarHeight + currentHeaderHeight;
  241. });
  242. // 方法:返回上一页
  243. const goBack = () => {
  244. uni.navigateBack();
  245. };
  246. // 方法:搜索输入
  247. const onSearchInput = (e) => {
  248. searchValue.value = e.detail.value;
  249. };
  250. // 方法:清除搜索
  251. const clearSearch = () => {
  252. searchValue.value = "";
  253. };
  254. // 方法:查看更多
  255. const viewMore = (market) => {
  256. console.log("查看更多:", market);
  257. uni.navigateTo({
  258. url: `/pages/home/marketDetail?market=${market}`,
  259. });
  260. };
  261. // 方法:查看指数详情
  262. const viewIndexDetail = (item) => {
  263. console.log("查看指数详情:", item.stockName);
  264. // uni.showToast({
  265. // title: `查看 ${item.stockName} 详情`,
  266. // icon: 'none',
  267. // duration: 2000
  268. // })
  269. // 这里可以跳转到具体的指数详情页面
  270. uni.navigateTo({
  271. url: `/pages/home/marketCondition?stockInformation=${encodeURIComponent(JSON.stringify(item))}`,
  272. });
  273. };
  274. // 生命周期:页面挂载
  275. onMounted(() => {
  276. // 获取系统信息
  277. const systemInfo = uni.getSystemInfoSync();
  278. iSMT.value = systemInfo.statusBarHeight || 0;
  279. console.log("全球指数页面加载完成");
  280. // 动态计算header实际高度
  281. uni
  282. .createSelectorQuery()
  283. .select(".header_fixed")
  284. .boundingClientRect((rect) => {
  285. if (rect) {
  286. headerHeight.value = rect.height;
  287. console.log("Header实际高度:", headerHeight.value, "px");
  288. }
  289. })
  290. .exec();
  291. // 检测warn文字是否溢出
  292. checkWarnTextOverflow();
  293. });
  294. // 监听headerHeight变化,重新计算contentHeight
  295. watch(headerHeight, (newHeight) => {
  296. if (newHeight > 0) {
  297. const systemInfo = uni.getSystemInfoSync();
  298. const windowHeight = systemInfo.windowHeight;
  299. const statusBarHeight = systemInfo.statusBarHeight || 0;
  300. const footerHeight = 100;
  301. contentHeight.value = windowHeight - statusBarHeight - newHeight - footerHeight;
  302. console.log("重新计算contentHeight:", contentHeight.value);
  303. }
  304. });
  305. </script>
  306. <style lang="scss" scoped>
  307. .main {
  308. position: relative;
  309. height: 100vh;
  310. overflow: hidden;
  311. background-color: #f5f5f5;
  312. }
  313. /* 状态栏占位 */
  314. .top {
  315. position: fixed;
  316. top: 0;
  317. left: 0;
  318. right: 0;
  319. z-index: 1001;
  320. background-color: #ffffff;
  321. }
  322. /* 固定头部样式 */
  323. .header_fixed {
  324. position: fixed;
  325. left: 0;
  326. right: 0;
  327. z-index: 1000;
  328. background-color: #ffffff;
  329. padding: 20rpx 0 0 0;
  330. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  331. }
  332. .header_content {
  333. display: flex;
  334. align-items: center;
  335. justify-content: space-between;
  336. height: 80rpx;
  337. padding: 0 20rpx;
  338. margin-bottom: 10rpx;
  339. }
  340. .header_back {
  341. margin-right: 20rpx;
  342. width: 25rpx;
  343. height: 30rpx;
  344. }
  345. .header_back image {
  346. width: 25rpx;
  347. height: 30rpx;
  348. }
  349. .header_input_wrapper {
  350. display: flex;
  351. align-items: center;
  352. width: 100%;
  353. margin: 0 20rpx 0 0;
  354. height: 70rpx;
  355. border-radius: 35rpx;
  356. background-color: #ffffff;
  357. border: 1rpx solid #e9ecef;
  358. padding: 0 80rpx 0 30rpx;
  359. font-size: 28rpx;
  360. color: #5c5c5c;
  361. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
  362. }
  363. .search_icon {
  364. width: 40rpx;
  365. height: 40rpx;
  366. opacity: 0.6;
  367. }
  368. .header_input {
  369. margin-left: 10rpx;
  370. }
  371. .header_icons {
  372. display: flex;
  373. align-items: center;
  374. gap: 15rpx;
  375. }
  376. .header_icon {
  377. width: 40rpx;
  378. height: 40rpx;
  379. display: flex;
  380. align-items: center;
  381. justify-content: center;
  382. }
  383. .header_icon image {
  384. width: 40rpx;
  385. height: 40rpx;
  386. }
  387. .warn {
  388. display: flex;
  389. align-items: center;
  390. justify-content: flex-start;
  391. gap: 10rpx;
  392. font-size: 28rpx;
  393. color: #666666;
  394. padding: 20rpx;
  395. max-width: 100%;
  396. overflow: hidden;
  397. position: relative;
  398. }
  399. .warn image {
  400. width: 40rpx;
  401. height: 40rpx;
  402. flex-shrink: 0;
  403. /* 防止图片被压缩 */
  404. position: relative;
  405. z-index: 2;
  406. /* 确保图片在最上层 */
  407. }
  408. .warn_text_container {
  409. flex: 1;
  410. overflow: hidden;
  411. position: relative;
  412. min-width: 0;
  413. /* 允许容器收缩 */
  414. }
  415. .warn_text {
  416. display: block;
  417. white-space: nowrap;
  418. will-change: transform;
  419. /* 优化动画性能 */
  420. }
  421. /* 文字滚动动画 */
  422. @keyframes scrollText {
  423. 0% {
  424. transform: translateX(0);
  425. }
  426. 20% {
  427. transform: translateX(0);
  428. }
  429. 80% {
  430. transform: translateX(-85%);
  431. }
  432. 100% {
  433. transform: translateX(-85%);
  434. }
  435. }
  436. /* 当文字超长时启用滚动动画 */
  437. .warn_text.scroll-active {
  438. animation: scrollText 12s linear infinite;
  439. animation-delay: 2s;
  440. /* 延迟2秒开始滚动,让用户先看到开头 */
  441. }
  442. /* 内容区域 */
  443. .content {
  444. position: fixed;
  445. left: 0;
  446. right: 0;
  447. bottom: 120rpx;
  448. background-color: #f5f5f5;
  449. padding: 0;
  450. }
  451. /* 市场分组 */
  452. .market-section {
  453. background-color: white;
  454. border-radius: 20rpx;
  455. }
  456. .market-header {
  457. margin: 20rpx 20rpx 0 20rpx;
  458. display: flex;
  459. align-items: center;
  460. justify-content: space-between;
  461. margin-bottom: 10rpx;
  462. padding-bottom: 10rpx;
  463. border-bottom: 2rpx solid #f0f0f0;
  464. }
  465. .market-title {
  466. font-size: 32rpx;
  467. font-weight: 600;
  468. color: #333;
  469. }
  470. .market-more {
  471. display: flex;
  472. align-items: center;
  473. gap: 8rpx;
  474. }
  475. .more-text {
  476. font-size: 24rpx;
  477. color: #666;
  478. }
  479. .more-arrow {
  480. font-size: 20rpx;
  481. color: #666;
  482. font-weight: bold;
  483. }
  484. /* 三列卡片网格 */
  485. .cards-grid-three {
  486. display: grid;
  487. grid-template-columns: repeat(3, 1fr);
  488. }
  489. .card-item {
  490. background-color: white;
  491. border-radius: 16rpx;
  492. overflow: hidden;
  493. transition: transform 0.2s ease, box-shadow 0.2s ease;
  494. }
  495. .card-item:active {
  496. transform: scale(0.98);
  497. box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.12);
  498. }
  499. /* 底部安全区域 */
  500. .bottom-safe-area {
  501. height: 40rpx;
  502. background-color: transparent;
  503. }
  504. /* 底部导航栏 */
  505. .static-footer {
  506. position: fixed;
  507. bottom: 0;
  508. left: 0;
  509. right: 0;
  510. z-index: 1000;
  511. }
  512. /* 响应式设计 */
  513. @media (max-width: 400rpx) {
  514. .cards-grid-three {
  515. grid-template-columns: repeat(2, 1fr);
  516. }
  517. }
  518. </style>