5 changed files with 299 additions and 36 deletions
-
1src/api/admin/index.ts
-
203src/components/blogs/ceshi.vue
-
36src/main.ts
-
4src/views/admin/BlogManageView.vue
-
85src/views/blog/blogcontent/BlogDetailView.vue
@ -1,30 +1,189 @@ |
|||||
<template> |
<template> |
||||
<a-form-item name="select-multiple" label="Select[multiple]"> |
|
||||
<a-select v-model:value="formState.labels" mode="multiple" placeholder="Please select favourite colors"> |
|
||||
<a-select-option :value="type.id" v-for="type in typelist" > |
|
||||
{{ type.color }} |
|
||||
</a-select-option> |
|
||||
</a-select> |
|
||||
</a-form-item> |
|
||||
|
<div> |
||||
|
<div |
||||
|
v-for="anchor in titles" |
||||
|
:style="{ padding: `10px 0 10px ${anchor.indent}px` }" |
||||
|
@click="handleAnchorClick(anchor)" |
||||
|
:key="anchor.lineIndex" |
||||
|
> |
||||
|
<a style="cursor: pointer">{{ anchor.title }}</a> |
||||
|
</div> |
||||
|
<v-md-preview :text="text" ref="preview" /> |
||||
|
</div> |
||||
</template> |
</template> |
||||
|
|
||||
<script setup lang='ts'> |
|
||||
import { ref } from 'vue'; |
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, onMounted } from 'vue'; |
||||
|
|
||||
const formState = ref({ |
|
||||
labels: [2, 3] // 初始值设定为 typelist 中的 id 值 |
|
||||
|
const text = ` |
||||
|
# heading 1 |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
|
||||
|
## heading 2 |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
|
||||
|
### heading 3 |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontentcontentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
contentcontentcontent |
||||
|
|
||||
|
|
||||
|
`; |
||||
|
|
||||
|
const preview = ref<any>(null); |
||||
|
const titles = ref<{ title: string; lineIndex: string; indent: number }[]>([]); |
||||
|
|
||||
|
onMounted(() => { |
||||
|
const anchors = document.querySelectorAll('h1, h2, h3, h4, h5, h6'); |
||||
|
const titlesArray = Array.from(anchors).filter((title) => !!title.innerText.trim()); |
||||
|
|
||||
|
if (!titlesArray.length) { |
||||
|
titles.value = []; |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const hTags = Array.from(new Set(titlesArray.map((title) => title.tagName))).sort(); |
||||
|
|
||||
|
titles.value = titlesArray.map((el) => ({ |
||||
|
title: el.innerText, |
||||
|
lineIndex: el.getAttribute('data-v-md-line') || '', // 确认属性名是否正确 |
||||
|
indent: 20 * (parseInt(el.tagName.substring(1)) - 1) // 确认缩进计算是否正确 |
||||
|
})); |
||||
}); |
}); |
||||
|
|
||||
const typelist = ref([ |
|
||||
{ |
|
||||
id: 2, |
|
||||
color: 'red' |
|
||||
}, |
|
||||
{ |
|
||||
id: 3, |
|
||||
color: 'green' |
|
||||
|
function handleAnchorClick(anchor: { title: string; lineIndex: string }) { |
||||
|
if (preview.value) { |
||||
|
const heading = preview.value.$el.querySelector(`[data-v-md-line="${anchor.lineIndex}"]`); |
||||
|
|
||||
|
if (heading) { |
||||
|
preview.value.scrollToTarget({ |
||||
|
target: heading, |
||||
|
scrollContainer: window, |
||||
|
top: 60, |
||||
|
}); |
||||
|
} else { |
||||
|
console.error(`Heading element not found for lineIndex: ${anchor.lineIndex}`); |
||||
|
} |
||||
|
} else { |
||||
|
console.error('Preview component not initialized.'); |
||||
} |
} |
||||
]); |
|
||||
|
} |
||||
</script> |
</script> |
||||
|
|
||||
<style></style> |
|
@ -1,12 +1,81 @@ |
|||||
<template> |
<template> |
||||
<div>{{ route.params.id }}</div> |
|
||||
</template> |
|
||||
|
<div id="blogDetail"> |
||||
|
<div v-for="anchor in titles" :style="{ padding: `10px 0 10px ${anchor.indent * 20}px` }" |
||||
|
@click="handleAnchorClick(anchor)" :key="anchor.lineIndex"> |
||||
|
<a style="cursor: pointer">{{ anchor.title }}</a> |
||||
|
</div> |
||||
|
<v-md-preview v-if="text.blogcontent" :text="text.blogcontent" v-bind="previewProps" /> |
||||
|
</div> |
||||
|
</template> |
||||
|
|
||||
<script setup lang='ts'> |
|
||||
import { useRoute } from 'vue-router'; |
|
||||
const route=useRoute() |
|
||||
</script> |
|
||||
|
<script setup lang="ts"> |
||||
|
import { ref, onMounted } from 'vue'; |
||||
|
import { useRoute } from 'vue-router'; |
||||
|
import { get } from '@/tools/request'; |
||||
|
|
||||
<style> |
|
||||
|
const route = useRoute(); |
||||
|
const text = ref({ |
||||
|
blogtitle: "", |
||||
|
blogcontent: "" |
||||
|
}); |
||||
|
const titles = ref<{ title: string; lineIndex: string; indent: number }[]>([]); |
||||
|
const preview = ref<any>(null); |
||||
|
|
||||
|
// 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 blog content |
||||
|
const blogOneList = async () => { |
||||
|
try { |
||||
|
const response = await get(`/blogs/list/${route.params.id}`); |
||||
|
if (response.data.data) { |
||||
|
text.value.blogcontent = response.data.data.blogcontent; |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error("Error fetching blog content:", error); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Process headings after blog content is fetched |
||||
|
const processHeadings = () => { |
||||
|
const anchors = document.querySelectorAll('#blogDetail h1, #blogDetail h2, #blogDetail h3, #blogDetail h4, #blogDetail h5, #blogDetail h6'); |
||||
|
const titlesArray = Array.from(anchors).filter((title) => !!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(`#blogDetail [data-v-md-line="${anchor.lineIndex}"]`); |
||||
|
|
||||
|
if (heading) { |
||||
|
heading.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
||||
|
} |
||||
|
}; |
||||
|
|
||||
|
// Lifecycle hook to fetch blog content and process headings |
||||
|
onMounted(async () => { |
||||
|
await blogOneList(); |
||||
|
processHeadings(); |
||||
|
}); |
||||
|
</script> |
||||
|
|
||||
|
<style scoped> |
||||
|
#blogDetail { |
||||
|
width: 45%; |
||||
|
} |
||||
|
</style> |
||||
|
|
||||
</style> |
|
Write
Preview
Loading…
Cancel
Save
Reference in new issue