feat: 功能3 - Markdown 渲染和代码高亮 (v0.9.8)
- 添加 MarkdownMessage.vue 组件 - 集成 markdown-it 库 - 集成 highlight.js 代码高亮 - 支持标题/列表/代码块/表格等 - ChatWindow 使用 Markdown 渲染 - 添加 .gitignore 忽略 node_modules
This commit is contained in:
13
.gitignore
vendored
13
.gitignore
vendored
@@ -1,11 +1,2 @@
|
|||||||
__pycache__/
|
frontend/node_modules/
|
||||||
*.pyc
|
frontend/package-lock.json
|
||||||
*.pyo
|
|
||||||
.pytest_cache/
|
|
||||||
*.db
|
|
||||||
*.sqlite
|
|
||||||
.env
|
|
||||||
venv/
|
|
||||||
*.pyc
|
|
||||||
__pycache__/
|
|
||||||
venv/
|
|
||||||
|
|||||||
@@ -9,15 +9,15 @@
|
|||||||
"typecheck": "vue-tsc --noEmit"
|
"typecheck": "vue-tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"vue": "^3.4.0",
|
"@highlightjs/vue-plugin": "^2.1.0",
|
||||||
"vue-router": "^4.2.0",
|
"axios": "^1.6.0",
|
||||||
|
"date-fns": "^3.0.0",
|
||||||
|
"highlight.js": "^11.11.1",
|
||||||
|
"markdown-it": "^14.1.1",
|
||||||
"pinia": "^2.1.0",
|
"pinia": "^2.1.0",
|
||||||
"socket.io-client": "^4.7.0",
|
"socket.io-client": "^4.7.0",
|
||||||
"axios": "^1.6.0",
|
"vue": "^3.4.0",
|
||||||
"markdown-it": "^14.0.0",
|
"vue-router": "^4.2.0"
|
||||||
"highlight.js": "^11.9.0",
|
|
||||||
"@highlightjs/vue-plugin": "^2.1.0",
|
|
||||||
"date-fns": "^3.0.0"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@vitejs/plugin-vue": "^5.0.0",
|
"@vitejs/plugin-vue": "^5.0.0",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, watch, nextTick } from 'vue'
|
import { ref, computed, watch, nextTick } from 'vue'
|
||||||
import { useChatStore } from '@/stores/chat'
|
import { useChatStore } from '@/stores/chat'
|
||||||
|
import MarkdownMessage from '@/components/chat/MarkdownMessage.vue'
|
||||||
|
|
||||||
const chatStore = useChatStore()
|
const chatStore = useChatStore()
|
||||||
const messagesContainer = ref<HTMLElement | null>(null)
|
const messagesContainer = ref<HTMLElement | null>(null)
|
||||||
@@ -64,7 +65,7 @@ function isOwnMessage(msg: any): boolean {
|
|||||||
<span class="message-time">{{ formatTime(message.created_at) }}</span>
|
<span class="message-time">{{ formatTime(message.created_at) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-bubble">
|
<div class="message-bubble">
|
||||||
{{ message.content }}
|
<MarkdownMessage :content="message.content" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
119
frontend/src/components/chat/MarkdownMessage.vue
Normal file
119
frontend/src/components/chat/MarkdownMessage.vue
Normal 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>
|
||||||
Reference in New Issue
Block a user