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.

897 lines
19 KiB

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