Container.vue 3.9 KB

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