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.

566 lines
13 KiB

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