feat: 功能3 - Markdown 渲染和代码高亮 (v0.9.8)

- 添加 MarkdownMessage.vue 组件
- 集成 markdown-it 库
- 集成 highlight.js 代码高亮
- 支持标题/列表/代码块/表格等
- ChatWindow 使用 Markdown 渲染
- 添加 .gitignore 忽略 node_modules
This commit is contained in:
2026-03-15 12:13:06 +08:00
parent 021ce8b50b
commit dcf8494db7
4 changed files with 130 additions and 19 deletions

13
.gitignore vendored
View File

@@ -1,11 +1,2 @@
__pycache__/
*.pyc
*.pyo
.pytest_cache/
*.db
*.sqlite
.env
venv/
*.pyc
__pycache__/
venv/
frontend/node_modules/
frontend/package-lock.json

View File

@@ -9,15 +9,15 @@
"typecheck": "vue-tsc --noEmit"
},
"dependencies": {
"vue": "^3.4.0",
"vue-router": "^4.2.0",
"@highlightjs/vue-plugin": "^2.1.0",
"axios": "^1.6.0",
"date-fns": "^3.0.0",
"highlight.js": "^11.11.1",
"markdown-it": "^14.1.1",
"pinia": "^2.1.0",
"socket.io-client": "^4.7.0",
"axios": "^1.6.0",
"markdown-it": "^14.0.0",
"highlight.js": "^11.9.0",
"@highlightjs/vue-plugin": "^2.1.0",
"date-fns": "^3.0.0"
"vue": "^3.4.0",
"vue-router": "^4.2.0"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.0.0",

View File

@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, computed, watch, nextTick } from 'vue'
import { useChatStore } from '@/stores/chat'
import MarkdownMessage from '@/components/chat/MarkdownMessage.vue'
const chatStore = useChatStore()
const messagesContainer = ref<HTMLElement | null>(null)
@@ -64,7 +65,7 @@ function isOwnMessage(msg: any): boolean {
<span class="message-time">{{ formatTime(message.created_at) }}</span>
</div>
<div class="message-bubble">
{{ message.content }}
<MarkdownMessage :content="message.content" />
</div>
</div>
</div>

View File

@@ -0,0 +1,119 @@
<script setup lang="ts">
import { computed } from 'vue'
import MarkdownIt from 'markdown-it'
import hljs from 'highlight.js'
import 'highlight.js/styles/github-dark.css'
const props = defineProps<{
content: string
}>()
const md = new MarkdownIt({
highlight: function (str, lang) {
if (lang && hljs.getLanguage(lang)) {
try {
return hljs.highlight(str, { language: lang }).value
} catch (__) {}
}
return hljs.highlightAuto(str).value
},
linkify: true,
breaks: true
})
const renderedContent = computed(() => {
return md.render(props.content)
})
</script>
<template>
<div class="markdown-body" v-html="renderedContent" />
</template>
<style scoped>
.markdown-body {
color: inherit;
line-height: 1.6;
}
.markdown-body :deep(h1),
.markdown-body :deep(h2),
.markdown-body :deep(h3),
.markdown-body :deep(h4),
.markdown-body :deep(h5),
.markdown-body :deep(h6) {
margin-top: 16px;
margin-bottom: 8px;
font-weight: 600;
}
.markdown-body :deep(h1) { font-size: 1.5em; }
.markdown-body :deep(h2) { font-size: 1.3em; }
.markdown-body :deep(h3) { font-size: 1.1em; }
.markdown-body :deep(h4) { font-size: 1em; }
.markdown-body :deep(p) {
margin-bottom: 12px;
}
.markdown-body :deep(ul),
.markdown-body :deep(ol) {
margin-bottom: 12px;
padding-left: 24px;
}
.markdown-body :deep(li) {
margin-bottom: 4px;
}
.markdown-body :deep(pre) {
background: #1e1e1e;
border-radius: 8px;
padding: 16px;
overflow-x: auto;
margin-bottom: 12px;
}
.markdown-body :deep(code) {
font-family: 'Fira Code', 'Monaco', 'Consolas', monospace;
font-size: 0.9em;
}
.markdown-body :deep(:not(pre) > code) {
background: rgba(0, 0, 0, 0.1);
padding: 2px 6px;
border-radius: 4px;
}
.markdown-body :deep(a) {
color: var(--primary-color);
text-decoration: none;
}
.markdown-body :deep(a:hover) {
text-decoration: underline;
}
.markdown-body :deep(blockquote) {
border-left: 4px solid var(--primary-color);
padding-left: 16px;
margin-left: 0;
color: var(--text-secondary);
}
.markdown-body :deep(table) {
border-collapse: collapse;
width: 100%;
margin-bottom: 12px;
}
.markdown-body :deep(th),
.markdown-body :deep(td) {
border: 1px solid var(--border-color);
padding: 8px;
}
.markdown-body :deep(th) {
background: var(--bg-secondary);
}
</style>