Container.vue 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <view class="container">
  3. <view
  4. class="title"
  5. :style="{
  6. paddingTop: `${safeArea.top}px`,
  7. background: bgColor,
  8. }"
  9. v-if="showTitle"
  10. >
  11. <uni-nav-bar
  12. :left-icon="showBack ? 'arrow-left' : ''"
  13. @clickLeft="onBack"
  14. :border="false"
  15. :title="title"
  16. fixed
  17. :backgroundColor="bgColor"
  18. />
  19. </view>
  20. <scroll-view
  21. :scroll-y="scrollY"
  22. :scroll-with-animation="true"
  23. :enable-flex="true"
  24. :enable-passive="true"
  25. :enhanced="true"
  26. :paging-enabled="true"
  27. :scroll-anchoring="true"
  28. :show-scrollbar="false"
  29. :scroll-x="scrollX"
  30. :scroll-into-view="scrollIntoView"
  31. enable-back-to-top
  32. scroll-anchoring
  33. >
  34. <view
  35. :class="['scroll-view', scrollX ? 'topic-scroll' : '']"
  36. :style="{
  37. height: `${safeArea.height}px`,
  38. whiteSpace: scrollX ? 'nowrap' : 'normal',
  39. }"
  40. >
  41. <slot></slot>
  42. </view>
  43. <!-- 底部文字 -->
  44. <view v-if="showBottom && bottomText" class="bottom-text">
  45. {{ bottomText }}
  46. </view>
  47. </scroll-view>
  48. <view
  49. class="bottom-button"
  50. :style="{
  51. width: `${safeArea.width}px`,
  52. background: bgColor,
  53. }"
  54. >
  55. <slot name="footer" :footer-height="footerHeight"></slot>
  56. </view>
  57. </view>
  58. </template>
  59. <script setup>
  60. import pages from '@/pages.json'
  61. import { ref, onMounted, watchEffect } from "vue";
  62. import { getRoute, router } from "../../utils/router";
  63. import { getRect } from "../../utils/utils";
  64. import { getCurrentInstance } from "vue";
  65. const props = defineProps({
  66. showBottom: {
  67. type: Boolean,
  68. default: false,
  69. },
  70. bottomText: {
  71. type: String,
  72. default: "~已经到底了~",
  73. },
  74. scrollX: {
  75. type: Boolean,
  76. default: false,
  77. },
  78. scrollY: {
  79. type: Boolean,
  80. default: true,
  81. },
  82. className: {
  83. type: String,
  84. default: "",
  85. },
  86. scrollIntoView: {
  87. type: String,
  88. default: "",
  89. },
  90. style: {
  91. type: [String, Object],
  92. default: "",
  93. },
  94. onBack: Function,
  95. showBack: {
  96. type: Boolean,
  97. default: true,
  98. },
  99. showTitle: {
  100. type: Boolean,
  101. default: true,
  102. },
  103. bgColor: {
  104. type: String,
  105. default: "#fff",
  106. },
  107. title: String
  108. });
  109. const emit = defineEmits(["onSafeAreaChange"]);
  110. const onBack = async () => {
  111. if (props.onBack) {
  112. await props.onBack();
  113. router.back();
  114. return;
  115. }
  116. router.back();
  117. };
  118. const safeArea = ref({
  119. footer: {},
  120. title: {},
  121. });
  122. const footerHeight = ref(0);
  123. onMounted(() => {
  124. const systemInfo = uni.getWindowInfo();
  125. // 判断是否为tabbar页面
  126. safeArea.value = {
  127. ...systemInfo.safeArea,
  128. source: systemInfo.safeArea,
  129. };
  130. const isTarbarPage = pages.tabBar.list.map(item => item.pagePath).includes(getRoute().routeList[0].path)
  131. if (isTarbarPage) {
  132. // 高度再减掉tabbar高度
  133. safeArea.value.height -= 50;
  134. // 减去系统导航栏高度
  135. safeArea.value.height -= (systemInfo.statusBarHeight - 18);
  136. }
  137. // 24是内边距
  138. safeArea.value.width -= 24;
  139. const instance = getCurrentInstance();
  140. // 获取头部高度
  141. getRect({
  142. name: ".title",
  143. onSuccess(res) {
  144. safeArea.value.title = res;
  145. safeArea.value.height -= props.showTitle
  146. ? safeArea.value.title?.height
  147. : 0;
  148. },
  149. instance,
  150. });
  151. // 获取底部高度
  152. getRect({
  153. name: ".bottom-button",
  154. onSuccess(res) {
  155. safeArea.value.footer = res;
  156. safeArea.value.height -= safeArea.value.footer?.height;
  157. safeArea.value.height += 22; // 剩余高度
  158. },
  159. instance,
  160. });
  161. });
  162. watchEffect(() => {
  163. emit("onSafeAreaChange", safeArea.value);
  164. });
  165. defineExpose({
  166. safeArea: safeArea.value,
  167. });
  168. </script>
  169. <style lang="scss" scoped>
  170. @import "@/uni.scss";
  171. .title {
  172. position: sticky;
  173. top: 0;
  174. z-index: 9999;
  175. }
  176. .container {
  177. display: flex;
  178. flex-direction: column;
  179. color: #333;
  180. box-sizing: content-box;
  181. position: relative;
  182. height: 100vh;
  183. }
  184. .scroll-view {
  185. position: relative;
  186. padding: 24rpx 24rpx 0;
  187. box-sizing: content-box;
  188. }
  189. .bottom-text {
  190. display: flex;
  191. align-items: center;
  192. justify-content: center;
  193. width: 100%;
  194. }
  195. .bottom-button {
  196. width: 100%;
  197. padding: 24rpx;
  198. position: absolute;
  199. bottom: 0;
  200. }
  201. .topic-scroll {
  202. white-space: nowrap;
  203. display: inline-flex;
  204. }
  205. </style>