CommonFunctionsPopup.vue 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168
  1. <template>
  2. <Teleport to="body">
  3. <Transition name="fade">
  4. <div v-if="visible" class="modal-mask" @click="handleClose"></div>
  5. </Transition>
  6. <Transition name="slide-up">
  7. <div v-if="visible" class="modal-panel" @click.stop >
  8. <div class="panel-handle-wrap">
  9. <div class="panel-handle"></div>
  10. </div>
  11. <div class="panel-title">常用功能</div>
  12. <div class="grid-container">
  13. <div
  14. v-for="(item, index) in menuItems"
  15. :key="index"
  16. class="grid-item"
  17. @click="handleItemClick(item)"
  18. >
  19. <div class="icon-box">
  20. <img
  21. :src="getIconPath(item.icon)"
  22. class="grid-icon-img"
  23. loading="lazy"
  24. />
  25. </div>
  26. <span class="label">{{ item.name }}</span>
  27. </div>
  28. </div>
  29. <div class="safe-area-bottom"></div>
  30. </div>
  31. </Transition>
  32. <router-view></router-view>
  33. <!-- <router-view v-slot="{ Component }">-->
  34. <!-- <transition name="fade">-->
  35. <!-- <component :is="Component" />-->
  36. <!-- </transition>-->
  37. <!-- </router-view>-->
  38. </Teleport>
  39. </template>
  40. <script setup>
  41. import { ref, onMounted, watch, onUnmounted } from 'vue' // 引入 watch 和 onUnmounted
  42. import { useRouter,useRoute } from 'vue-router'
  43. const route = useRoute() // 获取当前路由信息
  44. const router = useRouter()
  45. // 定义一个内部变量控制显示,用于触发 Transition 动画
  46. const visible = ref(false)
  47. const lockScroll = () => {
  48. // 强制隐藏 body 的滚动条
  49. document.body.style.overflow = 'hidden'
  50. // 如果是 iOS,有些情况需要锁 html
  51. document.documentElement.style.overflow = 'hidden'
  52. }
  53. const unlockScroll = () => {
  54. // 恢复滚动
  55. document.body.style.overflow = ''
  56. document.documentElement.style.overflow = ''
  57. }
  58. // 监听弹窗显示状态
  59. watch(visible, (newVal) => {
  60. if (newVal) {
  61. lockScroll() // 弹窗开 -> 锁死
  62. } else {
  63. unlockScroll() // 弹窗关 -> 解锁
  64. }
  65. })
  66. // 1. 进场动画:组件挂载后,立即设为 true
  67. onMounted(() => {
  68. if (route.name === 'BitcoinFunctions') {
  69. visible.value = true
  70. }
  71. })
  72. // 2. 离场逻辑:先关动画,等动画播完再路由回退
  73. const handleClose = () => {
  74. visible.value = false
  75. // 300ms 对应 CSS 中的 transition 时间
  76. setTimeout(() => {
  77. router.back()
  78. // router.push({ name: 'bitcoin' })
  79. }, 300)
  80. }
  81. const handleItemClick = (item) => {
  82. console.log('选中:', item.name)
  83. if (item.name === '交易设置') {
  84. router.push({ name: 'TradeSettings' })
  85. } else if (item.name === '交易规则') {
  86. router.push({ name: 'TradeRules' })
  87. } else if (item.name === '计算器') {
  88. router.push({ name: 'calculator' })
  89. } else {
  90. console.log('点击了其他:', item.name)
  91. }
  92. // 可以在这里处理业务逻辑,然后关闭
  93. // handleClose()
  94. }
  95. // Webpack 图片加载逻辑
  96. const getIconPath = (iconName) => {
  97. try {
  98. return require(`../../../assets/icon/bitcoin/${iconName}`)
  99. } catch (e) {
  100. return ''
  101. }
  102. }
  103. const menuItems = [
  104. { name: '交易设置', icon: 'shezhi.svg' },
  105. { name: '交易记录', icon: 'jilu.svg' },
  106. { name: '计算器', icon: 'jisuan.svg' },
  107. { name: '费率', icon: 'feilv.svg' },
  108. { name: '资金划转', icon: 'huazhuan.svg' },
  109. { name: 'OTC交易', icon: 'jiaoyi.svg' },
  110. { name: '交易规则', icon: 'guizhe.svg' },
  111. ]
  112. </script>
  113. <style scoped>
  114. /* 样式完全保持不变 */
  115. .modal-mask {
  116. position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
  117. background-color: rgba(0, 0, 0, 0.5); z-index: 199;
  118. }
  119. .modal-panel {
  120. position: fixed; bottom: 0; left: 50%; transform: translateX(-50%);
  121. width: 100%; max-width: 600px; background: white;
  122. border-radius: 16px 16px 0 0; padding: 10px 0 0 0; z-index: 199;
  123. }
  124. .panel-handle-wrap { display: flex; justify-content: center; padding-bottom: 20px; }
  125. .panel-handle { width: 36px; height: 4px; background: #E0E0E0; border-radius: 2px; }
  126. .panel-title { font-size: 18px; font-weight: bold; color: #333; padding: 0 20px 20px 20px; }
  127. .safe-area-bottom { height: 30px; }
  128. /* 动画 */
  129. .fade-enter-active, .fade-leave-active { transition: opacity 0.3s ease; }
  130. .fade-enter-from, .fade-leave-to { opacity: 0; }
  131. .slide-up-enter-active, .slide-up-leave-active { transition: transform 0.3s ease; }
  132. .slide-up-enter-from, .slide-up-leave-to { transform: translate(-50%, 100%); }
  133. /* Grid */
  134. .grid-container {
  135. display: grid; grid-template-columns: repeat(4, 1fr);
  136. row-gap: 25px; padding: 0 10px 20px 10px;
  137. }
  138. .grid-item {
  139. display: flex; flex-direction: column; align-items: center; cursor: pointer;
  140. }
  141. .icon-box {
  142. width: 40px; height: 40px; display: flex; align-items: center; justify-content: center;
  143. margin-bottom: 8px;
  144. }
  145. .grid-icon-img {
  146. width: 100%; height: 100%; object-fit: contain; display: block;
  147. }
  148. .label { font-size: 12px; color: #666; text-align: center; }
  149. </style>