4 changed files with 120 additions and 112 deletions
-
3src/components/blogs/header/NavigateMenu.vue
-
51src/components/blogs/leftsite/CataloGue.vue
-
18src/stores/index.ts
-
160src/views/blog/ContentDetail.vue
@ -1,11 +1,42 @@ |
|||
<template> |
|||
|
|||
</template> |
|||
|
|||
<script setup lang='ts'> |
|||
|
|||
</script> |
|||
|
|||
<style> |
|||
|
|||
</style> |
|||
<div> |
|||
<div class="catalogue" v-for="anchor in titles" :style="{ padding: `4px 0 4px ${anchor.indent * 20}px` }" |
|||
@click="handleAnchorClick(anchor)" :key="anchor.lineIndex"> |
|||
<a style="cursor: pointer">{{ anchor.title }}</a> |
|||
</div> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { computed } from 'vue'; |
|||
import { useContentStore } from '@/stores/index'; |
|||
|
|||
const store = useContentStore(); |
|||
|
|||
const titles = computed(() => store.titles); |
|||
|
|||
const handleAnchorClick = (anchor: { title: string; lineIndex: string }) => { |
|||
const heading = document.querySelector(`[data-v-md-line="${anchor.lineIndex}"]`); |
|||
|
|||
if (heading) { |
|||
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|||
} else { |
|||
const observer = new MutationObserver((mutations, obs) => { |
|||
const heading = document.querySelector(`[data-v-md-line="${anchor.lineIndex}"]`); |
|||
if (heading) { |
|||
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|||
obs.disconnect(); |
|||
} |
|||
}); |
|||
const container = document.querySelector('#content-container') as HTMLElement; |
|||
observer.observe(container, { childList: true, subtree: true }); |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.catalogue { |
|||
cursor: pointer; |
|||
} |
|||
</style> |
|||
|
@ -1,100 +1,74 @@ |
|||
<template> |
|||
<div class="contentDetail"> |
|||
<div :id="containerId"> |
|||
<div v-for="anchor in titles" :style="{ padding: `4px 0 4px ${anchor.indent * 20}px` }" |
|||
@click="handleAnchorClick(anchor)" :key="anchor.lineIndex"> |
|||
<a style="cursor: pointer">{{ anchor.title }}</a> |
|||
</div> |
|||
<a-card> |
|||
<v-md-preview v-if="text.content" :text="text.content" v-bind="previewProps" /> |
|||
<v-md-preview v-if="store.content.text" :text="store.content.text" v-bind="previewProps" /> |
|||
</a-card> |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, onMounted, nextTick } from 'vue'; |
|||
import { useRoute } from 'vue-router'; |
|||
import { get } from '@/tools/request'; |
|||
|
|||
const route = useRoute(); |
|||
|
|||
const props = defineProps<{ |
|||
apiUrl: string; |
|||
contentField: string; |
|||
containerId: string; |
|||
}>(); |
|||
|
|||
const text = ref({ content: "" }); |
|||
|
|||
const titles = ref<{ title: string; lineIndex: string; indent: number }[]>([]); |
|||
|
|||
// Props for v-md-preview component |
|||
const previewProps = { |
|||
leftToolbar: "undo redo clear | h bold italic strikethrough quote | ul ol table hr | link image code | save | tip | emoji" |
|||
}; |
|||
|
|||
// Fetch content |
|||
const fetchContent = async () => { |
|||
try { |
|||
const response = await get(`${props.apiUrl}/${route.params.id}`); |
|||
if (response.data.data) { |
|||
text.value.content = response.data.data[props.contentField]; |
|||
} |
|||
} catch (error) { |
|||
console.error("Error fetching content:", error); |
|||
</div> |
|||
</template> |
|||
|
|||
<script setup lang="ts"> |
|||
import { ref, onMounted, nextTick } from 'vue'; |
|||
import { useRoute } from 'vue-router'; |
|||
import { get } from '@/tools/request'; |
|||
import { useContentStore } from '@/stores'; |
|||
|
|||
const route = useRoute(); |
|||
const store = useContentStore(); |
|||
|
|||
const props = defineProps<{ |
|||
apiUrl: string; |
|||
contentField: string; |
|||
containerId: string; |
|||
}>(); |
|||
|
|||
// Props for v-md-preview component |
|||
const previewProps = { |
|||
leftToolbar: "undo redo clear | h bold italic strikethrough quote | ul ol table hr | link image code | save | tip | emoji" |
|||
}; |
|||
|
|||
// Fetch content |
|||
const fetchContent = async () => { |
|||
try { |
|||
const response = await get(`${props.apiUrl}/${route.params.id}`); |
|||
if (response.data.data) { |
|||
store.setContent(response.data.data[props.contentField]); |
|||
} |
|||
}; |
|||
|
|||
// Process headings after content is fetched |
|||
const processHeadings = async () => { |
|||
await nextTick(); |
|||
const anchors = document.querySelectorAll(`#${props.containerId} h1, #${props.containerId} h2, #${props.containerId} h3, #${props.containerId} h4, #${props.containerId} h5, #${props.containerId} h6`); |
|||
const titlesArray = Array.from(anchors).filter((title: any) => !!title.innerText.trim()); |
|||
|
|||
if (!titlesArray.length) { |
|||
titles.value = []; |
|||
return; |
|||
} |
|||
|
|||
const hTags = Array.from(new Set(titlesArray.map((title: any) => title.tagName))).sort(); |
|||
|
|||
titles.value = titlesArray.map((el: any) => ({ |
|||
title: el.innerText, |
|||
lineIndex: el.getAttribute('data-v-md-line') || '', |
|||
indent: hTags.indexOf(el.tagName), |
|||
})); |
|||
}; |
|||
|
|||
// Handle anchor click |
|||
const handleAnchorClick = (anchor: { title: string; lineIndex: string }) => { |
|||
const heading = document.querySelector(`#${props.containerId} [data-v-md-line="${anchor.lineIndex}"]`); |
|||
|
|||
if (heading) { |
|||
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|||
} else { |
|||
// Use MutationObserver to wait for the target element to be available |
|||
const observer = new MutationObserver((mutations, obs) => { |
|||
const heading = document.querySelector(`#${props.containerId} [data-v-md-line="${anchor.lineIndex}"]`); |
|||
if (heading) { |
|||
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|||
obs.disconnect(); // Stop observing once the target element is found |
|||
} |
|||
}); |
|||
const container = document.querySelector(`#${props.containerId}`) as HTMLElement; |
|||
observer.observe(container, { childList: true, subtree: true }); |
|||
} |
|||
}; |
|||
|
|||
// Lifecycle hook to fetch content and process headings |
|||
onMounted(async () => { |
|||
await fetchContent(); |
|||
await processHeadings(); |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
#blogDetail, #diaryDetail { |
|||
width: 45%; |
|||
margin: 0 24px; |
|||
} catch (error) { |
|||
console.error("Error fetching content:", error); |
|||
} |
|||
}; |
|||
|
|||
// Process headings after content is fetched |
|||
const processHeadings = async () => { |
|||
const anchors = document.querySelectorAll(`#${props.containerId} h1, #${props.containerId} h2, #${props.containerId} h3, #${props.containerId} h4, #${props.containerId} h5, #${props.containerId} h6`); |
|||
const titlesArray = Array.from(anchors).filter((title: any) => !!title.innerText.trim()); |
|||
|
|||
if (!titlesArray.length) { |
|||
store.setTitles([]); |
|||
return; |
|||
} |
|||
</style> |
|||
|
|||
|
|||
const hTags = Array.from(new Set(titlesArray.map((title: any) => title.tagName))).sort(); |
|||
|
|||
store.setTitles(titlesArray.map((el: any) => ({ |
|||
title: el.innerText, |
|||
lineIndex: el.getAttribute('data-v-md-line') || '', |
|||
indent: hTags.indexOf(el.tagName), |
|||
}))); |
|||
}; |
|||
|
|||
// Lifecycle hook to fetch content and process headings |
|||
onMounted(async () => { |
|||
await fetchContent(); |
|||
await processHeadings(); |
|||
}); |
|||
</script> |
|||
|
|||
<style scoped> |
|||
.contentDetail { |
|||
width: 45%; |
|||
margin: 0 24px; |
|||
} |
|||
</style> |
Write
Preview
Loading…
Cancel
Save
Reference in new issue