优化报告导出功能和Vditor显示

- 实现自定义导出格式选择框,向上延展显示
- 修复Vditor导出功能,使用正确的按钮选择器
- 添加导出后Vditor内容自动恢复机制
- 优化用户体验,支持PDF、HTML、Markdown、纯文本导出
- 添加详细的调试日志和错误处理
- 修复导出后Vditor内容消失的问题
This commit is contained in:
lixinran 2025-10-15 13:40:37 +08:00
parent b635c1fd08
commit e7ea6b98c8
1 changed files with 192 additions and 13 deletions

View File

@ -122,17 +122,41 @@
<!-- 操作按钮 -->
<div>
<h3 class="text-lg font-semibold text-gray-900 mb-3">操作</h3>
<div class="flex flex-wrap gap-3">
<div class="flex flex-wrap gap-3 relative">
<!-- 下载按钮 -->
<div class="relative export-menu-container">
<button
@click="downloadReport"
@click="toggleExportMenu"
class="flex items-center gap-2 bg-primary-600 text-white px-4 py-2 rounded-lg hover:bg-primary-700 transition-colors"
>
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 10v6m0 0l-3-3m3 3l3-3m2 8H7a2 2 0 01-2-2V5a2 2 0 0 1 2-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
</svg>
下载报告
<svg class="w-4 h-4 transition-transform duration-200" :class="{ 'rotate-180': showExportMenu }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
<!-- 导出格式选择菜单 -->
<div
v-if="showExportMenu"
class="absolute bottom-full left-0 mb-1 bg-white border border-gray-200 rounded-lg shadow-lg z-50 min-w-[160px]"
>
<div class="py-1">
<button
v-for="format in exportFormats"
:key="format.key"
@click="exportReport(format.key)"
class="w-full flex items-center gap-3 px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 transition-colors"
>
<span class="text-lg">{{ format.icon }}</span>
<span>{{ format.label }}</span>
</button>
</div>
</div>
</div>
<button
@click="shareReport"
class="flex items-center gap-2 bg-gray-100 text-gray-700 px-4 py-2 rounded-lg hover:bg-gray-200 transition-colors"
@ -163,7 +187,7 @@
</template>
<script setup lang="ts">
import { ref, watch, onUnmounted, nextTick } from 'vue'
import { ref, watch, onUnmounted, onMounted, nextTick } from 'vue'
import Vditor from 'vditor'
// Props
@ -182,6 +206,15 @@ const emit = defineEmits(['close', 'navigate', 'get-task-title'])
// Vditor
const vditorInstance = ref<Vditor | null>(null)
//
const showExportMenu = ref(false)
const exportFormats = [
{ key: 'pdf', label: 'PDF', icon: '📄' },
{ key: 'html', label: 'HTML', icon: '🌐' },
{ key: 'markdown', label: 'Markdown', icon: '📝' },
{ key: 'txt', label: '纯文本', icon: '📃' }
]
// Markdown
const cleanMarkdownContent = (content: string): string => {
if (!content) return ''
@ -281,6 +314,24 @@ const initVditor = async () => {
if (toolbar) {
console.log('- 工具栏可见性:', getComputedStyle(toolbar).display)
console.log('- 工具栏宽度:', getComputedStyle(toolbar).width)
//
const exportButtons = toolbar.querySelectorAll('[data-type*="export"]')
console.log('- 导出按钮数量:', exportButtons.length)
exportButtons.forEach((btn, index) => {
console.log(`- 导出按钮${index + 1}:`, btn.getAttribute('data-type'), btn.textContent)
})
//
const allButtons = toolbar.querySelectorAll('button, [data-type]')
console.log('- 所有工具栏按钮:')
allButtons.forEach((btn, index) => {
const dataType = btn.getAttribute('data-type')
const text = btn.textContent?.trim()
if (dataType || text) {
console.log(` ${index + 1}. data-type: ${dataType}, text: "${text}"`)
}
})
}
}
}, 500)
@ -326,9 +377,23 @@ const destroyVditor = () => {
}
//
const handleClickOutside = (event: Event) => {
const target = event.target as HTMLElement
if (!target.closest('.export-menu-container')) {
showExportMenu.value = false
}
}
//
onMounted(() => {
document.addEventListener('click', handleClickOutside)
})
//
onUnmounted(() => {
destroyVditor()
document.removeEventListener('click', handleClickOutside)
})
const getTypeClass = (type: string) => {
@ -353,18 +418,132 @@ const getTypeText = (type: string) => {
// emit
const downloadReport = () => {
//
const toggleExportMenu = () => {
showExportMenu.value = !showExportMenu.value
}
// Vditor
const ensureVditorExists = () => {
if (!vditorInstance.value && props.currentReport?.content) {
console.log('Vditor实例不存在重新初始化...')
nextTick(() => {
if (props.drawerOpen && props.currentReport) {
setTimeout(() => {
if (props.drawerOpen && props.currentReport) {
initVditor()
}
}, 100)
}
})
}
}
//
const exportReport = (format: string) => {
if (!props.currentReport) return
//
showExportMenu.value = false
console.log(`开始导出${format}格式的报告`)
console.log('Vditor实例状态:', vditorInstance.value ? '存在' : '不存在')
// Vditor
ensureVditorExists()
// Vditor使Vditor
if (vditorInstance.value && format !== 'txt') {
try {
const vditorElement = document.getElementById('vditor-report-content')
if (vditorElement) {
console.log('Vditor元素找到开始查找导出按钮')
//
let exportButton: HTMLElement | null = null
// 1Vditor
const toolbarButtons = vditorElement.querySelectorAll('.vditor-toolbar button, .vditor-toolbar [data-type]')
console.log('找到的工具栏按钮数量:', toolbarButtons.length)
//
toolbarButtons.forEach((btn, index) => {
const element = btn as HTMLElement
console.log(`按钮${index}:`, {
text: element.textContent?.trim(),
dataType: element.getAttribute('data-type'),
className: element.className,
title: element.getAttribute('title')
})
})
// 2
if (format === 'pdf') {
exportButton = vditorElement.querySelector('[data-type="pdf"]') as HTMLElement
} else if (format === 'html') {
exportButton = vditorElement.querySelector('[data-type="html"]') as HTMLElement
} else if (format === 'markdown') {
exportButton = vditorElement.querySelector('[data-type="markdown"]') as HTMLElement
}
console.log(`查找${format}导出按钮结果:`, exportButton)
//
if (exportButton) {
console.log(`找到${format}导出按钮,准备点击`)
exportButton.click()
console.log(`已点击${format}导出按钮`)
// Vditor
setTimeout(() => {
if (!vditorInstance.value && props.currentReport?.content) {
console.log('导出后重新初始化Vditor')
initVditor()
}
}, 1000)
return
}
// 3
const generalExportButton = vditorElement.querySelector('[data-type="export"]') as HTMLElement ||
vditorElement.querySelector('[title*="导出"]') as HTMLElement ||
vditorElement.querySelector('[title*="export"]') as HTMLElement ||
vditorElement.querySelector('button[title*="导出"]') as HTMLElement
if (generalExportButton) {
console.log('找到通用导出按钮,准备点击')
generalExportButton.click()
console.log('已点击通用导出按钮')
// Vditor
setTimeout(() => {
if (!vditorInstance.value && props.currentReport?.content) {
console.log('导出后重新初始化Vditor')
initVditor()
}
}, 1000)
return
}
console.log('未找到任何导出按钮')
}
} catch (error) {
console.warn(`Vditor导出${format}失败:`, error)
}
}
//
console.log(`使用备用下载方案,格式:${format}`)
const blob = new Blob([props.currentReport.content], { type: 'text/plain;charset=utf-8' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `${props.currentReport.title}.txt`
a.download = `${props.currentReport.title}.${format === 'txt' ? 'txt' : format}`
a.click()
URL.revokeObjectURL(url)
}
const shareReport = () => {
if (!props.currentReport) return