SimpleRichHtml.vue 3.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. <template>
  2. <div v-show="show" ref="scrollContainer" class="nana-rich-html-container">
  3. <div class="rich-html">
  4. <slot name="prepend" />
  5. <template
  6. v-for="(content, i) in contents"
  7. :key="i"
  8. >
  9. <div
  10. v-if="content && content != 'null'"
  11. :data-r-id="id"
  12. class="content"
  13. v-html="content"
  14. />
  15. </template>
  16. <slot name="append" />
  17. </div>
  18. <div class="rich-html-catalog" v-if="catalog && catalogItems.length > 0">
  19. <CommonCatalog
  20. :items="catalogItems"
  21. :scrollContainer="scrollContainer"
  22. />
  23. </div>
  24. </div>
  25. </template>
  26. <script setup lang="ts">
  27. import CommonUtils from '@/common/utils/CommonUtils';
  28. import CommonCatalog, { type CatalogItem } from '../content/CommonCatalog.vue';
  29. import { onBeforeUnmount, onMounted, ref, watch, type PropType } from 'vue';
  30. const props = defineProps({
  31. contents: {
  32. type: Array as PropType<string[]>,
  33. default: () => ([]),
  34. },
  35. tagStyle: {
  36. type: Object as PropType<Record<string, string>>,
  37. default: () => ({}),
  38. },
  39. catalog: {
  40. type: Boolean,
  41. default: true,
  42. },
  43. noStyle: {
  44. type: Boolean,
  45. default: false,
  46. },
  47. show: {
  48. type: Boolean,
  49. default: true,
  50. },
  51. })
  52. const id = CommonUtils.genNonDuplicateIDHEX(12);
  53. const catalogItems = ref<CatalogItem[]>([]);
  54. const scrollContainer = ref<HTMLElement|null>(null);
  55. let lastStyleTag : HTMLElement|null = null;
  56. function genTagCss() {
  57. if (Object.keys(props.tagStyle).length > 0) {
  58. const style = document.createElement('style');
  59. let css = '';
  60. for (const key in props.tagStyle) {
  61. css += `.rich-html div[data-r-id="${id}"] ${key} { ${props.tagStyle[key]} } `
  62. }
  63. style.innerHTML = css;
  64. document.body.appendChild(style);
  65. lastStyleTag = style;
  66. }
  67. }
  68. function generateCatalog() {
  69. catalogItems.value = [];
  70. if (!props.catalog)
  71. return;
  72. let anchrId = 0;
  73. for (let i = 1; i <= 6; i++) {
  74. const heades = document.querySelectorAll(`.rich-html div[data-r-id="${id}"] h${i}`);
  75. for (const header of heades) {
  76. anchrId++;
  77. if (header instanceof HTMLHeadingElement) {
  78. if (header.id == '')
  79. header.id = 'header' + anchrId + 'a' + CommonUtils.genNonDuplicateIDHEX(12);
  80. catalogItems.value.push({
  81. title: header.textContent || '',
  82. scrollPos: header.offsetTop,
  83. level: i,
  84. anchor: header.id,
  85. });
  86. }
  87. }
  88. }
  89. catalogItems.value.sort((a, b) => {
  90. return a.scrollPos - b.scrollPos;
  91. })
  92. }
  93. watch(() => props.contents, () => {
  94. if (import.meta.server)
  95. return;
  96. setTimeout(() => {
  97. generateCatalog();
  98. }, 200);
  99. }, { immediate: true })
  100. onBeforeUnmount(() => {
  101. if (import.meta.server)
  102. return;
  103. if (lastStyleTag) {
  104. lastStyleTag.parentElement?.removeChild(lastStyleTag);
  105. lastStyleTag = null;
  106. }
  107. })
  108. onMounted(() => {
  109. if (import.meta.server)
  110. return;
  111. genTagCss()
  112. });
  113. </script>
  114. <style lang="scss">
  115. .nana-rich-html-container {
  116. position: relative;
  117. display: flex;
  118. flex-direction: row;
  119. .rich-html {
  120. flex: 1 1 100%;
  121. }
  122. .rich-html-catalog {
  123. position: fixed;
  124. top: 140px;
  125. right: 0;
  126. bottom: 0px;
  127. width: 15rem;
  128. }
  129. }
  130. @media (max-width: 1648px) {
  131. .rich-html-catalog {
  132. display: none;
  133. }
  134. }
  135. </style>