Files
ebiz-ai-knowledge-manage/src/components/RenderMinerU/index.vue
陈昱达 a3df6f38a5 feat(RenderMinerU): 优化 PDF 渲染组件功能
- 添加 isShowPdf 属性,控制 PDF 预览的显示- 调整编辑模式下的界面布局
- 优化按钮生成事件的命名
- 添加初始 Markdown 文档获取方法
2025-04-14 16:47:54 +08:00

314 lines
9.9 KiB
Vue

<template>
<div style="height: 100%;">
<div class="mv10 mh20 text-right" v-if="isEdit">
<el-button type="primary" size="medium" @click="saveMarkDown">保存并处理</el-button>
<el-button size="medium">取消</el-button>
</div>
<div :class="!isEdit ? 'mt10 flex' : 'flex'" style="height:calc(100% - 35px);flex:1">
<iframe
v-if="isShowPdf"
id="iframe"
:src="`${iframeSrc}/pdfjs-dist/web/viewer.html?file=${encodeURIComponent(prdUrl)}`"
class="miner-u el-card is-always-shadow ml20"
></iframe>
<div style="flex:1;" class="mh20 miner-u-md">
<el-tabs type="border-card" style="height: 100%;overflow: hidden" @tab-click="changeTab">
<el-tab-pane label="预览" style="overflow:scroll;">
<div v-html="markdownHtml" class="view-body" id="viewBody" ref="viewBody"></div>
</el-tab-pane>
<el-tab-pane label="编辑" :disabled="!isEdit" v-if="isEdit">
<div class="lineH25 view-body" contenteditable id="md-editor" @blur="emitMarkDown" v-html="markdown">
<!-- <pre id="viewBody"></pre>-->
</div>
</el-tab-pane>
</el-tabs>
</div>
</div>
</div>
</template>
<script>
import { getPdfUrl, minerUBbox, minerUMarkDown, minerUMarkDownUpdate } from '@/api/generatedApi/index'
import { DEFAULT_COLOR_SECTION, PDF_COLOR_PICKER } from './pdf-color'
import MarkdownIt from 'markdown-it'
import markdownItKatex from 'markdown-it-katex'
const md = new MarkdownIt({
html: true
}).use(markdownItKatex)
export default {
name: 'index',
data() {
return {
tableIdCounter: 0,
prdUrl: ``,
iframeSrc: window.location.origin,
bboxList: [],
markdown: '',
md,
markdownHtml: '',
page: 1,
copyTable: null,
tableActionButtons: [
{
label: '重新识别',
icon: 'el-icon-edit',
click: tableElement => {
console.log('重新识别')
}
},
{
label: '编辑',
icon: 'el-icon-edit',
click: tableElement => {
this.copyTable = tableElement.innerHTML
tableElement.classList.remove('m-view')
tableElement.setAttribute('contenteditable', 'true')
let buttonContainer = tableElement.querySelector('.md-editor-setting')
buttonContainer.innerHTML = null
this.generateButton(tableElement, buttonContainer, this.tableActionConfirm)
}
}
],
tableActionConfirm: [
{
label: '返回',
icon: 'el-icon-edit',
click: tableElement => {
tableElement.classList.add('m-view')
tableElement.setAttribute('contenteditable', 'false')
let buttonContainer = tableElement.querySelector('.md-editor-setting')
buttonContainer.innerHTML = null
this.generateButton(tableElement, buttonContainer, this.tableActionButtons)
}
}
// {
// label: '取消',
// icon: 'el-icon-edit',
// click: tableElement => {}
// }
]
}
},
props: {
documentId: {
type: String,
default: '1361351897324294144'
},
isEdit: {
type: Boolean,
default: true
},
isShowPdf: {
type: Boolean,
default: true
}
},
watch: {},
components: {},
filters: {},
methods: {
// 封装 按钮生成事件
generateButton(tableElement, buttonContainer, actionButtons) {
let contenteditable = tableElement.getAttribute('contenteditable')
let buttons = !actionButtons ? (contenteditable === 'false' ? this.tableActionButtons : this.tableActionConfirm) : actionButtons
// 循环按钮配置
for (let i = 0; i < buttons.length; i++) {
const button = document.createElement('button')
button.innerText = buttons[i].label
button.className = 'el-button el-button--primary el-button--mini editor-button'
button.style.pointerEvents = 'auto'
button.addEventListener('click', () => {
buttons[i].click(tableElement)
})
buttonContainer.appendChild(button)
}
},
// 生成视觉模型
viewHtmlModel() {
this.$nextTick(() => {
const mdHtml = document.getElementById('md-editor')
mdHtml.addEventListener('mouseover', e => {
const tableElement = e.target.closest('table')
if (tableElement) {
// 检查是否已经存在按钮容器
let buttonContainer = tableElement.querySelector('.md-editor-setting')
if (!buttonContainer) {
buttonContainer = document.createElement('div')
buttonContainer.style.position = 'absolute'
buttonContainer.style.pointerEvents = 'none'
buttonContainer.style.zIndex = '9999'
buttonContainer.className = 'md-editor-setting'
buttonContainer.setAttribute('contenteditable', 'false')
this.generateButton(tableElement, buttonContainer)
// 设置按钮位置在表格正中间浮动
const rect = tableElement.getBoundingClientRect()
buttonContainer.style.left = `10px` // 调整位置
buttonContainer.style.top = `-20px` // 调整位置
tableElement.appendChild(buttonContainer)
}
}
})
mdHtml.addEventListener('mouseout', e => {
const tableElement = e.target.closest('table')
if (!tableElement || !tableElement.contains(e.relatedTarget)) {
const buttonContainer = tableElement.querySelector('.md-editor-setting')
if (buttonContainer) {
buttonContainer.remove()
}
}
})
})
},
// 导出
emitMarkDown() {
let pre = document.getElementById('md-editor').innerText
this.$emit('getMarkDownIt', { innerText: pre })
},
saveMarkDown() {
let pre = document.getElementById('md-editor').innerText
minerUMarkDownUpdate({
documentId: this.documentId,
newMd: pre
})
},
// 给文件增加色块
formatJson(data) {
return data.map(item => {
let bboxes = []
// 处理预处理块
if (item.preproc_blocks && item.preproc_blocks.length > 0) {
item.preproc_blocks.forEach(block => {
bboxes.push({
type: block.type,
bbox: block.bbox,
color: PDF_COLOR_PICKER[block.type] || DEFAULT_COLOR_SECTION
})
})
}
// 处理丢弃块
if (item.discarded_blocks && item.discarded_blocks.length > 0) {
item.discarded_blocks.forEach(block => {
bboxes.push({
type: block.type,
bbox: block.bbox,
color: PDF_COLOR_PICKER[block.type] || DEFAULT_COLOR_SECTION
})
})
}
return {
...item,
bboxes
}
})
},
// bbox 解析 传递 颜色
getPDFDetailBbox() {
minerUBbox({ documentId: this.documentId }).then(res => {
this.bboxList = this.formatJson(JSON.parse(JSON.stringify(res.content.content)))
})
},
changeTab() {
let pre = document.getElementById('md-editor').innerHTML
// pre = JSON.stringify(pre)
// let copyMdHtml = md.render(pre)
// 给 copyMdHtml 里面的table 增加 class m-view
// copyMdHtml = copyMdHtml.re
this.markdownHtml = md.render(pre.replace(/class="m-view"/g, ''))
},
// 初始md 文档
async getPDFDetailMarkDown() {
const response = await fetch(minerUMarkDown({ documentId: this.documentId }))
this.markdown = await response.text()
this.markdown = this.markdown
.replace(/<table/g, () => {
const uniqueId = `table-${this.tableIdCounter++}`
return `<table contenteditable='false' id="${uniqueId}" class="m-view"`
})
.replace(/<script/g, '< script')
this.markdownHtml = this.md.render(this.markdown.replace(/class="m-view"/g, ''))
},
// 向 iframe 发送消息
sendMessageToIframe(type, message) {
// 获取 iframe 元素
const iframe = document.getElementById('iframe')
// 检查 iframe 是否存在并且可以发送消息
if (iframe && iframe.contentWindow) {
iframe.contentWindow.postMessage(
{
type,
data: message
},
'*'
)
}
}
},
created() {
this.getPDFDetailBbox()
this.getPDFDetailMarkDown()
this.prdUrl = getPdfUrl({ documentId: this.documentId })
},
mounted() {
// 监听 iframe 的 postMessage 事件
window.addEventListener('message', event => {
// 检查消息来源是否合法
if (event.origin === window.location.origin) {
// 根据消息状态执行不同操作
switch (event.data.status) {
case 'loaded':
// 格式化 JSON 数据并发送给 iframe
this.sendMessageToIframe('initExtractLayerData', this.bboxList)
this.sendMessageToIframe('title', '')
break
}
}
if (event.data.pageNum) {
const num = event.data.pageNum || 1
this.sendMessageToIframe('pageChange', num)
}
if (event.data.pageNumDetail) {
const pageNumDetail = event.data.pageNumDetail || 1
this.page = pageNumDetail
this.sendMessageToIframe('pageNumDetail', pageNumDetail)
}
})
this.viewHtmlModel()
},
computed: {}
}
</script>
<style scoped lang="scss">
.miner-u {
outline: none;
border: none;
flex: 1;
border-radius: unset;
//width:500px;
//height:100%
}
#md-editor {
//超过宽度自动折行
white-space: pre-wrap;
word-wrap: break-word;
overflow: auto;
outline: none;
border: none;
}
.miner-u-md {
/deep/ .el-tabs--border-card > .el-tabs__content {
height: calc(100% - 30px);
overflow: auto;
}
.tabs__content {
}
}
</style>