修复Vditor工具栏显示异常问题

- 在main.ts中添加Vditor CSS样式引入
- 修复ReportDrawer.vue中Vditor配置
- 使用官方文档推荐的完整工具栏配置
- 添加nextTick确保DOM完全渲染
- 修复工具栏pin配置和版本号匹配
This commit is contained in:
lixinran 2025-10-15 12:58:36 +08:00
parent 1b75c934b3
commit b635c1fd08
2 changed files with 157 additions and 45 deletions

View File

@ -77,11 +77,11 @@
<!-- 报告内容 --> <!-- 报告内容 -->
<div class="prose max-w-none"> <div class="prose max-w-none">
<!-- 有内容时显示HTML渲染的内容 --> <!-- 有内容时显示Vditor渲染的Markdown -->
<div <div
v-if="props.currentReport?.content && props.currentReport.content.trim()" v-if="props.currentReport?.content && props.currentReport.content.trim()"
id="vditor-report-content"
class="text-gray-700 leading-relaxed" class="text-gray-700 leading-relaxed"
v-html="getRenderedContent()"
></div> ></div>
<!-- 无内容时显示提示信息 --> <!-- 无内容时显示提示信息 -->
@ -163,7 +163,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
// VditorVue import { ref, watch, onUnmounted, nextTick } from 'vue'
import Vditor from 'vditor'
// Props // Props
const props = defineProps({ const props = defineProps({
@ -178,6 +179,9 @@ const props = defineProps({
// Emits // Emits
const emit = defineEmits(['close', 'navigate', 'get-task-title']) const emit = defineEmits(['close', 'navigate', 'get-task-title'])
// Vditor
const vditorInstance = ref<Vditor | null>(null)
// Markdown // Markdown
const cleanMarkdownContent = (content: string): string => { const cleanMarkdownContent = (content: string): string => {
if (!content) return '' if (!content) return ''
@ -192,35 +196,6 @@ const cleanMarkdownContent = (content: string): string => {
} }
// MarkdownHTML
const markdownToHtml = (markdown: string): string => {
if (!markdown) return ''
let html = markdown
//
html = html.replace(/^### (.*$)/gim, '<h3>$1</h3>')
html = html.replace(/^## (.*$)/gim, '<h2>$1</h2>')
html = html.replace(/^# (.*$)/gim, '<h1>$1</h1>')
//
html = html.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
html = html.replace(/\*(.*?)\*/g, '<em>$1</em>')
//
html = html.replace(/^- (.*$)/gim, '<li>$1</li>')
html = html.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>')
//
html = html.replace(/\n\n/g, '</p><p>')
html = '<p>' + html + '</p>'
//
html = html.replace(/<p><\/p>/g, '')
html = html.replace(/<p>\s*<\/p>/g, '')
return html
}
// //
const getReportContent = () => { const getReportContent = () => {
@ -228,13 +203,133 @@ const getReportContent = () => {
return cleanMarkdownContent(props.currentReport.content) return cleanMarkdownContent(props.currentReport.content)
} }
// HTML // Vditor
const getRenderedContent = () => { const initVditor = async () => {
const content = getReportContent() //
return markdownToHtml(content) if (!props.drawerOpen || !props.currentReport) {
return
} }
// props //
destroyVditor()
// DOM
const element = document.getElementById('vditor-report-content')
if (!element) {
console.error('Vditor容器元素不存在')
return
}
//
element.innerHTML = ''
try {
vditorInstance.value = new Vditor('vditor-report-content', {
height: 500,
toolbar: [
'emoji', 'headings', 'bold', 'italic', 'strike', 'link', '|',
'list', 'ordered-list', 'check', 'outdent', 'indent', '|',
'quote', 'line', 'code', 'inline-code', 'insert-before', 'insert-after', '|',
'table', '|', 'undo', 'redo', '|', 'fullscreen', 'edit-mode', '|',
'content-theme', 'code-theme', 'export', 'outline', 'preview', 'devtools', 'info', 'help'
],
toolbarConfig: {
pin: true,
},
cache: {
enable: false,
},
preview: {
theme: {
current: 'light',
path: 'https://unpkg.com/vditor@3.11.2/dist/css/content-theme'
}
},
mode: 'wysiwyg',
after: () => {
//
if (!props.drawerOpen || !props.currentReport) {
destroyVditor()
return
}
console.log('Vditor初始化完成工具栏已显示')
//
if (vditorInstance.value) {
try {
vditorInstance.value.setValue(getReportContent())
console.log('Vditor内容设置完成')
} catch (error) {
console.warn('Vditor设置内容失败:', error)
}
}
//
setTimeout(() => {
const vditorElement = document.getElementById('vditor-report-content')
if (vditorElement) {
const toolbar = vditorElement.querySelector('.vditor-toolbar')
const editor = vditorElement.querySelector('.vditor-wysiwyg')
const preview = vditorElement.querySelector('.vditor-preview')
console.log('Vditor元素检查:')
console.log('- 工具栏存在:', !!toolbar)
console.log('- 编辑器存在:', !!editor)
console.log('- 预览存在:', !!preview)
if (toolbar) {
console.log('- 工具栏可见性:', getComputedStyle(toolbar).display)
console.log('- 工具栏宽度:', getComputedStyle(toolbar).width)
}
}
}, 500)
}
})
} catch (error) {
console.error('Vditor初始化失败:', error)
vditorInstance.value = null
}
}
//
watch([() => props.currentReport, () => props.drawerOpen], ([newReport, isOpen]) => {
if (isOpen && newReport && newReport.content) {
// 使nextTickDOM
nextTick(() => {
if (props.drawerOpen && props.currentReport) {
//
setTimeout(() => {
if (props.drawerOpen && props.currentReport) {
initVditor()
}
}, 100)
}
})
} else if (!isOpen) {
// Vditor
destroyVditor()
}
}, { deep: true })
// Vditor
const destroyVditor = () => {
if (vditorInstance.value) {
try {
vditorInstance.value.destroy()
} catch (error) {
console.warn('Vditor销毁时出错:', error)
} finally {
vditorInstance.value = null
}
}
}
//
onUnmounted(() => {
destroyVditor()
})
const getTypeClass = (type: string) => { const getTypeClass = (type: string) => {
const typeClasses = { const typeClasses = {
@ -304,7 +399,23 @@ const copyToClipboard = async () => {
max-width: none; max-width: none;
} }
.prose h1 { /* Vditor样式 */
#vditor-report-content {
border: none !important;
background: transparent !important;
}
#vditor-report-content :deep(.vditor-preview) {
border: none !important;
padding: 0 !important;
margin: 0 !important;
background: transparent !important;
font-size: 14px !important;
line-height: 1.6 !important;
color: #374151 !important;
}
#vditor-report-content :deep(.vditor-preview h1) {
font-size: 1.875rem; font-size: 1.875rem;
font-weight: 700; font-weight: 700;
margin-top: 2rem; margin-top: 2rem;
@ -312,7 +423,7 @@ const copyToClipboard = async () => {
color: #111827; color: #111827;
} }
.prose h2 { #vditor-report-content :deep(.vditor-preview h2) {
font-size: 1.5rem; font-size: 1.5rem;
font-weight: 600; font-weight: 600;
margin-top: 1.5rem; margin-top: 1.5rem;
@ -320,7 +431,7 @@ const copyToClipboard = async () => {
color: #111827; color: #111827;
} }
.prose h3 { #vditor-report-content :deep(.vditor-preview h3) {
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 600; font-weight: 600;
margin-top: 1.25rem; margin-top: 1.25rem;
@ -328,29 +439,29 @@ const copyToClipboard = async () => {
color: #111827; color: #111827;
} }
.prose p { #vditor-report-content :deep(.vditor-preview p) {
margin-bottom: 1rem; margin-bottom: 1rem;
line-height: 1.7; line-height: 1.7;
color: #374151; color: #374151;
} }
.prose ul { #vditor-report-content :deep(.vditor-preview ul) {
margin-bottom: 1rem; margin-bottom: 1rem;
padding-left: 1.5rem; padding-left: 1.5rem;
} }
.prose li { #vditor-report-content :deep(.vditor-preview li) {
margin-bottom: 0.5rem; margin-bottom: 0.5rem;
line-height: 1.6; line-height: 1.6;
color: #374151; color: #374151;
} }
.prose strong { #vditor-report-content :deep(.vditor-preview strong) {
font-weight: 600; font-weight: 600;
color: #111827; color: #111827;
} }
.prose em { #vditor-report-content :deep(.vditor-preview em) {
font-style: italic; font-style: italic;
color: #4b5563; color: #4b5563;
} }

View File

@ -5,6 +5,7 @@ import App from './App.vue'
import './style.css' import './style.css'
import ElementPlus from 'element-plus' import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css' import 'element-plus/dist/index.css'
import 'vditor/dist/index.css'
const app = createApp(App) const app = createApp(App)