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.

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