- 安装依赖
npm install marked
npm install marked-highlight
npm install github-markdown-css
npm install highlight.js
npm install dompurify
- markdown组件
<template>
<div class="markdown-body" v-html="sanitizedHtml" />
<svg class="circular" viewBox="0 0 50 50" v-if="markLoad">
<circle class="path" cx="25" cy="25" r="20" fill="none"></circle>
</svg>
</template>
<script lang="ts" setup>
import { computed } from 'vue'
import DOMPurify from 'dompurify'
import { Marked } from 'marked'
import { markedHighlight } from 'marked-highlight'
import hljs from 'highlight.js'
import 'highlight.js/styles/atom-one-dark.css'
import 'github-markdown-css'
// 单例 Marked 配置
const marked = new Marked(
markedHighlight({
langPrefix: 'hljs language-',
highlight(code, lang) {
const validLang = hljs.getLanguage(lang) ? lang : 'plaintext'
try {
return hljs.highlight(code, { language: validLang }).value
} catch {
return hljs.highlightAuto(code).value
}
}
})
)
// Props 接收内容
const props = defineProps({
content: { type: String, required: true, default: "", },
markLoad: { type: Boolean, default: false }
})
// 安全处理流程
const sanitizedHtml = computed(() => {
const rawHtml = marked.parse(props.content) as string
return DOMPurify.sanitize(rawHtml, {
ADD_ATTR: ['target'] // 允许链接 target 属性
})
})
// 链接安全增强
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node.tagName === 'A') {
node.setAttribute('rel', 'noopener noreferrer')
}
})
</script>
<style lang="scss">
.markdown-body {
ul,
ol {
list-style: revert !important;
li {
list-style: inherit !important;
}
}
table {
width: auto;
th,
td {
border: 1px solid #ddd;
padding: 8px 12px;
}
tr:nth-child(even) {
background: #f8f8f8;
}
}
}
</style>
<style lang="scss" scoped>
.circular {
display: inline;
height: 24px;
width: 24px;
margin-top: 4px;
animation: loading-rotate 2s linear infinite
}
.path {
animation: loading-dash 1.5s ease-in-out infinite;
stroke-dasharray: 90, 150;
stroke-dashoffset: 0;
stroke-width: 2;
stroke: var(--el-color-primary);
stroke-linecap: round
}
@keyframes loading-rotate {
to {
transform: rotate(360deg)
}
}
@keyframes loading-dash {
0% {
stroke-dasharray: 1, 200;
stroke-dashoffset: 0
}
50% {
stroke-dasharray: 90, 150;
stroke-dashoffset: -40px
}
to {
stroke-dasharray: 90, 150;
stroke-dashoffset: -120px
}
}
</style>
- 父组件
<markdown-content :content="chatContent.msgcontent" :markLoad="chatContent.loading"></markdown-content>