feat: 优化md渲染相关逻辑,优化文章页面

This commit is contained in:
539943419 committed 2025-03-24 12:31:43 +08:00
1 parent e0d967f736
commit 370ec61f3d
5 files changed
+107 -90

No files matched your search

+4 -14
View File
@@ -25,32 +25,22 @@ const VNodeArr = computed(() => {
return () => [] return () => []
} }
const renderedData = markdownIt.render(props.data) const renderedData = markdownIt.render(props.data)
console.log({
renderedData
})
const h2v = new Html2VNode() const h2v = new Html2VNode()
h2v.use( h2v.use(
{ {
filter(tagName) { filter(tagName) {
return tagName === 'code' return tagName === 'code'
}, },
rander({ render({
item, item,
// tagName,
tagAttrs, tagAttrs,
// middlewareMap
}) { }) {
// console.log({
// tagName,
// tagAttrs,
// })
const codeType = tagAttrs?.class?.slice(1, -1)?.split('-')?.[1] ?? '' const codeType = tagAttrs?.class?.slice(1, -1)?.split('-')?.[1] ?? ''
// console.log({
// codeType,
// })
// @ts-ignore // @ts-ignore
const inline = (item.children[0].children.trim() as string).includes('\n') ? false : true const inline = (item.children[0].children.trim() as string).includes('\n') ? false : true
console.log({
inline,
c: item.children
})
return h( return h(
codeMirror, codeMirror,
{ {
+19 -22
View File
@@ -1,6 +1,6 @@
<template> <template>
<div class="posts-wrapper"> <div class="posts-wrapper">
<article class="posts-item" v-for="post in postsData" :key="post.id"> <article class="posts-item" v-for="post in posts" :key="post.id">
<header class="post-title"> <header class="post-title">
<nuxt-link :to="`/posts/post-${post.id}`">{{ post.title }}</nuxt-link> <nuxt-link :to="`/posts/post-${post.id}`">{{ post.title }}</nuxt-link>
</header> </header>
@@ -18,7 +18,7 @@
</div> </div>
</footer> </footer>
</article> </article>
<div class="posts-skeleton" v-if="!isInit"> <div class="posts-skeleton" v-if="pending">
<div class="posts-skeleton-item" v-for="_ in range(5)"> <div class="posts-skeleton-item" v-for="_ in range(5)">
<el-skeleton :rows="3" /> <el-skeleton :rows="3" />
</div> </div>
@@ -33,29 +33,26 @@ import bookmark from '~/components/icons/tag.vue'
import type { postItem } from '~/types/post'; import type { postItem } from '~/types/post';
const store = useDefaultStore() const store = useDefaultStore()
const isInit = ref(false)
const postsData = ref<postItem[]>([]) // 使用useFetch在服务器端获取数据
const { data, pending } = await useFetch<{ code: number, data: postItem[] }>('/api/posts/getPostList', {
const getData = async () => { // 如果缓存中已有数据,优先使用缓存
const cache = store.getCache('posts') key: 'posts-list',
if (cache) { transform: (response) => {
return cache as postItem[] if (response && response.data) {
store.setCache('posts', response.data)
return response
} }
const response = await $fetch('/api/posts/getPostList') return { code: 0, data: [] }
const res = response.data },
store.setCache('posts', res) // 添加以下选项
return res lazy: true, // 避免客户端自动重新获取
} server: true, // 仅在服务器端获取数据
// cache: 'force-cache', // 强制使用缓存
onMounted(async () => { // dedupe: 'force', // 强制使用缓存的数据
// router.push('/posts/temp')
// await sleep(1000)
postsData.value = await getData()
isInit.value = true;
// console.log(postsData.value)
}) })
// 从响应中提取文章列表
const posts = computed(() => data.value?.data || []) as ComputedRef<postItem[]>
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
+29 -41
View File
@@ -1,9 +1,9 @@
<template> <template>
<div class="md-wrapper"> <div class="md-wrapper">
<div class="md" v-if="isInit"> <div class="md" v-if="postData">
<div class="md-title">{{ title }}</div> <div class="md-title">{{ postData.title }}</div>
<div class="md-divider"></div> <div class="md-divider"></div>
<markdown-render :data="content"></markdown-render> <markdown-render :data="postData.content"></markdown-render>
</div> </div>
<div v-else class="md-skeleton-item"> <div v-else class="md-skeleton-item">
<el-skeleton :rows="12" /> <el-skeleton :rows="12" />
@@ -18,55 +18,43 @@ import markdownRender from '~/components/markdownRender.vue';
import type { postContent } from '~/types/post'; import type { postContent } from '~/types/post';
const postStore = usePostStore() const postStore = usePostStore()
const route = useRoute() const route = useRoute()
const isInit = ref(false)
const title = ref('')
const content = ref('')
const renderedData = ref('')
const id = route.params.id as string const id = route.params.id as string
onMounted(async () => { // 使用useFetch来获取文章内容,支持SSR
const { data: fetchedPost } = await useFetch('/api/posts/getPostContent', {
const _p = new Promise<postContent>(async (resolve, reject) => {
try {
const cache = postStore.getPostCache(id)
if (cache) {
resolve(cache)
return
}
const res = await $fetch('/api/posts/getPostContent', {
method: 'POST', method: 'POST',
body: { body: {
id id
} },
}) as postContent transform: async (response: postContent) => {
postStore.setPostCache(id, res) // 缓存数据
resolve(res) postStore.setPostCache(id, response)
} catch (error) {
reject(error)
}
})
// 渲染Markdown
const markdownIt = await useMarkdownit() const markdownIt = await useMarkdownit()
const postsData = await _p return {
title: response.title,
content.value = postsData.data content: response.data,
rendered: markdownIt.render(response.data)
renderedData.value = markdownIt.render(postsData.data) }
},
title.value = postsData.title // 使用缓存
async onRequest({ options }) {
isInit.value = true const cache = postStore.getPostCache(id)
if (cache) {
return {
title: cache.title,
content: cache.data,
rendered: (await useMarkdownit()).render(cache.data)
}
}
}
}) })
// 响应式数据
const postData = computed(() => fetchedPost.value)
</script> </script>
<style scoped> <style scoped>
+47 -6
View File
@@ -94,6 +94,49 @@ const formatTime = () => {
return `${hours}:${minutes}:${seconds}`; return `${hours}:${minutes}:${seconds}`;
}; };
// 定义API响应类型
interface SystemInfoResponse {
success: boolean;
data?: {
system: {
platform: string;
release: string;
hostname: string;
};
node: string;
hardware: {
cpu: {
model: string;
cores: number;
usage: number;
};
memory: {
total: string;
free: string;
used: string;
percent: number;
};
};
};
message?: string;
}
interface PerformanceResponse {
success: boolean;
data?: {
cpu: {
usage: number;
};
memory: {
total: string;
free: string;
used: string;
percent: number;
};
};
message?: string;
}
// 获取系统信息 // 获取系统信息
const fetchSystemInfo = async () => { const fetchSystemInfo = async () => {
try { try {
@@ -102,10 +145,9 @@ const fetchSystemInfo = async () => {
} }
error.value = ''; error.value = '';
const { data } = await useFetch('/api/system/info'); const response = await $fetch<SystemInfoResponse>('/api/system/info');
const response = data.value as any;
if (response && response.success) { if (response && response.success && response.data) {
systemInfo.value = response.data; systemInfo.value = response.data;
// 如果还没有性能数据,初始化它 // 如果还没有性能数据,初始化它
@@ -133,10 +175,9 @@ const fetchSystemInfo = async () => {
// 获取性能数据(CPU和内存使用率) // 获取性能数据(CPU和内存使用率)
const fetchPerformanceData = async () => { const fetchPerformanceData = async () => {
try { try {
const { data } = await useFetch('/api/system/performance'); const response = await $fetch<PerformanceResponse>('/api/system/performance');
const response = data.value as any;
if (response && response.success) { if (response && response.success && response.data) {
performanceData.value = response.data; performanceData.value = response.data;
performanceUpdated.value = formatTime(); performanceUpdated.value = formatTime();
} }
+8 -7
View File
@@ -12,10 +12,11 @@ export default class Html2VNode {
this.middlewareList.push(middleware) this.middlewareList.push(middleware)
} }
public render(htmlString: string) { public render(htmlString: string) {
const html2ObjResult = this.html2Obj(htmlString); const htmlString2 = htmlString.replaceAll('<hr>', '<hr></hr>').replaceAll('<br>', '<br></br>')
const html2ObjResult = this.html2Obj(htmlString2);
const middlewareMap = this.buildMiddlewareMap(html2ObjResult); const middlewareMap = this.buildMiddlewareMap(html2ObjResult);
const wrinklesResult = this.wrinkles(html2ObjResult) const wrinklesResult = this.wrinkles(html2ObjResult)
const vnodes = this.rander2VNode(wrinklesResult, middlewareMap) const vnodes = this.render2VNode(wrinklesResult, middlewareMap)
return vnodes; return vnodes;
} }
@@ -40,7 +41,7 @@ export default class Html2VNode {
return middlewareMap; return middlewareMap;
} }
private rander2VNode = ( private render2VNode = (
wrinklesResult: (miaoVNodeType | string)[], wrinklesResult: (miaoVNodeType | string)[],
middlewareMap: Map<string, middlewareType[]> middlewareMap: Map<string, middlewareType[]>
): (VNode | string)[] => { ): (VNode | string)[] => {
@@ -51,7 +52,7 @@ export default class Html2VNode {
const { tagName = '', tagAttrs } = this.parseTag(item.tag); const { tagName = '', tagAttrs } = this.parseTag(item.tag);
const middlewares = middlewareMap.get(tagName) const middlewares = middlewareMap.get(tagName)
for(const middleware of middlewares!) { for(const middleware of middlewares!) {
const _res = middleware.rander({ const _res = middleware.render({
item, item,
tagName, tagName,
tagAttrs, tagAttrs,
@@ -69,7 +70,7 @@ export default class Html2VNode {
filter: (_: string) => { filter: (_: string) => {
return true; return true;
}, },
rander: ({ render: ({
item, tagName, tagAttrs, middlewareMap item, tagName, tagAttrs, middlewareMap
}) => { }) => {
if (!tagName) { if (!tagName) {
@@ -77,7 +78,7 @@ export default class Html2VNode {
} }
let child: string | (string | VNode)[] = item.children as string let child: string | (string | VNode)[] = item.children as string
if (typeof item.children !== 'string') { if (typeof item.children !== 'string') {
child = this.rander2VNode(item.children, middlewareMap) child = this.render2VNode(item.children, middlewareMap)
} }
return h(tagName ?? 'span', tagAttrs, child) return h(tagName ?? 'span', tagAttrs, child)
} }
@@ -214,7 +215,7 @@ type TagType = {
// 要优化也是以后优化了 // 要优化也是以后优化了
export type middlewareType = { export type middlewareType = {
filter: (tagName: string) => boolean, filter: (tagName: string) => boolean,
rander: (option: { render: (option: {
item: miaoVNodeType, item: miaoVNodeType,
tagName: string, tagName: string,
tagAttrs: { [key: string]: string }, tagAttrs: { [key: string]: string },