CommonCatalog.vue 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  1. <script setup lang="ts">
  2. import SimpleScrollView from '../display/SimpleScrollView.vue';
  3. import { ref, watch, type PropType } from 'vue';
  4. export interface CatalogItem {
  5. title: string,
  6. level: number,
  7. scrollPos: number,
  8. anchor: string,
  9. }
  10. const emit = defineEmits([
  11. "goToItem"
  12. ])
  13. const props = defineProps({
  14. items: {
  15. type: Object as PropType<CatalogItem[]>,
  16. default: () => []
  17. },
  18. scrollContainer: {
  19. type: Object as PropType<HTMLElement|null>,
  20. default: () => null,
  21. },
  22. })
  23. const activeIndex = ref(-1);
  24. function handlerContainerScroll(e: Event) {
  25. const container = e.target as HTMLElement;
  26. const scrollTop = container.scrollTop;
  27. activeIndex.value = 0;
  28. for (let i = props.items.length - 1; i >= 0; i--) {
  29. const item = props.items[i];
  30. if (scrollTop >= item.scrollPos) {
  31. activeIndex.value = i;
  32. break;
  33. }
  34. }
  35. }
  36. function handlerItemClick(item: CatalogItem) {
  37. if (item.anchor) {
  38. const el = document.getElementById(item.anchor);
  39. if (el) {
  40. el.scrollIntoView({ behavior: 'smooth' });
  41. }
  42. }
  43. emit('goToItem', item);
  44. }
  45. watch(() => props.scrollContainer, (newVal, oldVal) => {
  46. if (oldVal && oldVal instanceof HTMLElement)
  47. oldVal.removeEventListener('scroll', handlerContainerScroll);
  48. if (newVal && newVal instanceof HTMLElement)
  49. newVal.addEventListener('scroll', handlerContainerScroll);
  50. }, { immediate: true });
  51. </script>
  52. <template>
  53. <SimpleScrollView class="nana-catalog" :scrollY="true">
  54. <div>
  55. <div
  56. v-for="(item, index) in props.items"
  57. :key="index"
  58. :class="[
  59. 'nana-catalog-item',
  60. `level-${item.level}`,
  61. activeIndex === index ? 'active' : '',
  62. ]"
  63. @click="handlerItemClick(item)"
  64. >
  65. {{ item.title }}
  66. </div>
  67. </div>
  68. </SimpleScrollView>
  69. </template>
  70. <style lang="scss">
  71. .nana-catalog {
  72. position: relative;
  73. margin-left: 0.5rem;
  74. > div {
  75. display: flex;
  76. flex-direction: column;
  77. }
  78. &::before {
  79. content: '';
  80. position: absolute;
  81. top: 0;
  82. bottom: 0;
  83. left: 0;
  84. width: 1px;
  85. background-color: var(--nana-text-6);
  86. }
  87. .nana-catalog-item {
  88. position: relative;
  89. padding: 0.4rem 0.8rem;
  90. font-size: 1rem;
  91. color: var(--nana-text-6);
  92. user-select: none;
  93. cursor: pointer;
  94. &.active {
  95. font-weight: bold;
  96. color: var(--nana-text-1);
  97. &::after {
  98. content: '';
  99. position: absolute;
  100. top: calc(50% - 6px);
  101. left: 0;
  102. border: 8px solid transparent;
  103. border-left: 8px solid var(--nana-text-1);
  104. }
  105. }
  106. &.level-1 {
  107. font-size: 1.2rem;
  108. padding-left: 1rem;
  109. }
  110. &.level-3,
  111. &.level-4,
  112. &.level-5 {
  113. font-size: 0.8rem;
  114. padding-left: 1.2rem;
  115. &::before {
  116. content: '·';
  117. display: inline-block;
  118. padding-right: 0.6rem;
  119. }
  120. }
  121. &.level-6 {
  122. font-size: 0.7rem;
  123. padding-left: 1.6rem;
  124. }
  125. }
  126. }
  127. </style>