topbar.uvue 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. <template>
  2. <cl-sticky>
  3. <cl-topbar fixed safe-area-top>
  4. <cl-tabs
  5. :list="list"
  6. v-model="active"
  7. height="36px"
  8. :gutter="20"
  9. @change="onTabChange"
  10. ></cl-tabs>
  11. <template #append>
  12. <view class="h-[44px] w-[30px] flex items-center justify-center mr-1">
  13. <cl-icon name="search-line"></cl-icon>
  14. </view>
  15. </template>
  16. </cl-topbar>
  17. </cl-sticky>
  18. </template>
  19. <script setup lang="ts">
  20. import { debounce, getSafeAreaHeight, isNull } from "@/cool";
  21. import { usePage, type ClTabsItem } from "@/uni_modules/cool-ui";
  22. import { getCurrentInstance, onMounted, onUnmounted, ref } from "vue";
  23. const { proxy } = getCurrentInstance()!;
  24. const page = usePage();
  25. // 当前激活tab
  26. const active = ref("info");
  27. // 滚动时激活tab
  28. const scrollActive = ref("");
  29. // 卡片距离顶部偏移量
  30. const tops = ref<number[]>([]);
  31. // tab项列表
  32. const list = ref<ClTabsItem[]>([
  33. { label: "商品", value: "info" },
  34. { label: "评价", value: "comment" },
  35. { label: "详情", value: "desc" }
  36. ]);
  37. /**
  38. * 获取所有.card顶部坐标
  39. */
  40. async function getTops(): Promise<void> {
  41. return new Promise((resolve) => {
  42. uni.createSelectorQuery()
  43. .in(proxy?.$root)
  44. .selectAll(".card")
  45. .boundingClientRect((res) => {
  46. const top = page.getScrollTop() - 44 - getSafeAreaHeight("top"); // 去头部高度
  47. // 只计算有id的card
  48. tops.value = (res as NodeInfo[])
  49. .filter((e) => e.id != "" && !isNull(e.id))
  50. .map((e) => (e.top ?? 0) + top);
  51. resolve();
  52. })
  53. .exec();
  54. });
  55. }
  56. /**
  57. * tab切换
  58. */
  59. async function onTabChange(value: string) {
  60. // 设置滚动时激活tab
  61. scrollActive.value = value;
  62. // 重新获取卡片位置
  63. await getTops();
  64. // 查找符合当前位置的tab索引
  65. const index = list.value.findIndex((e) => e.value == value);
  66. if (index < 0) return;
  67. // 滚动到对应卡片位置
  68. page.scrollTo(tops.value[index] + 1);
  69. }
  70. /**
  71. * 同步当前tab
  72. */
  73. const setActive = debounce(() => {
  74. active.value = scrollActive.value;
  75. }, 100);
  76. /**
  77. * 滚动时激活tab
  78. */
  79. function onScroll(top: number) {
  80. let index = -1;
  81. // 查找符合当前位置的tab索引
  82. for (let i = 0; i < tops.value.length; i++) {
  83. if (top >= tops.value[i]) {
  84. index = i;
  85. }
  86. }
  87. // 设置激活tab
  88. if (index >= 0 && index < list.value.length) {
  89. scrollActive.value = list.value[index].value as string;
  90. setActive();
  91. }
  92. }
  93. onMounted(() => {
  94. // 获取卡片位置
  95. getTops();
  96. // 监听页面滚动
  97. page.onScroll(onScroll);
  98. });
  99. onUnmounted(() => {
  100. // 移除监听
  101. page.offScroll(onScroll);
  102. });
  103. </script>