feat: 优化md渲染相关逻辑,优化文章页面
This commit is contained in:
1 parent
e0d967f736
commit
370ec61f3d
5 files changed
+113
-96
No files matched your search
@@ -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,
|
||||||
{
|
{
|
||||||
|
|||||||
+20
-23
@@ -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)
|
||||||
const response = await $fetch('/api/posts/getPostList')
|
return response
|
||||||
const res = response.data
|
}
|
||||||
store.setCache('posts', res)
|
return { code: 0, data: [] }
|
||||||
return res
|
},
|
||||||
}
|
// 添加以下选项
|
||||||
|
lazy: true, // 避免客户端自动重新获取
|
||||||
onMounted(async () => {
|
server: true, // 仅在服务器端获取数据
|
||||||
// router.push('/posts/temp')
|
// cache: 'force-cache', // 强制使用缓存
|
||||||
// await sleep(1000)
|
// dedupe: 'force', // 强制使用缓存的数据
|
||||||
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>
|
||||||
|
|||||||
+34
-46
@@ -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) => {
|
method: 'POST',
|
||||||
try {
|
body: {
|
||||||
const cache = postStore.getPostCache(id)
|
id
|
||||||
if (cache) {
|
},
|
||||||
resolve(cache)
|
transform: async (response: postContent) => {
|
||||||
return
|
// 缓存数据
|
||||||
}
|
postStore.setPostCache(id, response)
|
||||||
const res = await $fetch('/api/posts/getPostContent', {
|
|
||||||
method: 'POST',
|
// 渲染Markdown
|
||||||
body: {
|
const markdownIt = await useMarkdownit()
|
||||||
id
|
|
||||||
}
|
return {
|
||||||
}) as postContent
|
title: response.title,
|
||||||
postStore.setPostCache(id, res)
|
content: response.data,
|
||||||
resolve(res)
|
rendered: markdownIt.render(response.data)
|
||||||
} catch (error) {
|
|
||||||
reject(error)
|
|
||||||
}
|
}
|
||||||
})
|
},
|
||||||
|
// 使用缓存
|
||||||
const markdownIt = await useMarkdownit()
|
async onRequest({ options }) {
|
||||||
|
const cache = postStore.getPostCache(id)
|
||||||
const postsData = await _p
|
if (cache) {
|
||||||
|
return {
|
||||||
content.value = postsData.data
|
title: cache.title,
|
||||||
|
content: cache.data,
|
||||||
renderedData.value = markdownIt.render(postsData.data)
|
rendered: (await useMarkdownit()).render(cache.data)
|
||||||
|
}
|
||||||
title.value = postsData.title
|
}
|
||||||
|
}
|
||||||
isInit.value = true
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 响应式数据
|
||||||
|
const postData = computed(() => fetchedPost.value)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -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
@@ -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 },
|
||||||
|
|||||||
Reference in new issue
Block a user