mirror of
http://112.124.100.131/ebiz-ai/ebiz-ai-knowledge-manage.git
synced 2025-12-11 11:56:51 +08:00
feat(RenderMinerU): 优化表格识别和编辑功能
- 添加表格重新识别功能,支持对选定表格进行重新识别和编辑 - 优化表格编辑界面,增加撤销和重做功能 - 改进表格数据处理逻辑,提高识别准确性和编辑体验 -调整表格按钮样式和布局,增加图标和提示
This commit is contained in:
@@ -386,3 +386,12 @@ body .el-collapse-item__wrap {
|
|||||||
.editor-button {
|
.editor-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.public-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.public-icon + .public-icon {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<el-breadcrumb class="app-breadcrumb" separator-class="el-icon-arrow-right">
|
<el-breadcrumb class="app-breadcrumb" separator-class="el-icon-arrow-right">
|
||||||
<transition-group name="breadcrumb">
|
<transition-group name="breadcrumb">
|
||||||
<!-- <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">-->
|
<!-- <el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">-->
|
||||||
<el-breadcrumb-item v-for="(item, index) in levelList" :key="index">
|
<el-breadcrumb-item v-for="(item, index) in levelList" :key="item.path">
|
||||||
<span v-if="item.redirect === 'noRedirect' || index === levelList.length - 1" class="no-redirect fs16">{{ item.meta.title }}</span>
|
<span v-if="item.redirect === 'noRedirect' || index === levelList.length - 1" class="no-redirect fs16">{{ item.meta.title }}</span>
|
||||||
<span v-else>{{ item.meta.title }}</span>
|
<span v-else>{{ item.meta.title }}</span>
|
||||||
</el-breadcrumb-item>
|
</el-breadcrumb-item>
|
||||||
|
|||||||
@@ -20,9 +20,7 @@
|
|||||||
<div v-html="markdownHtml" class="view-body" id="viewBody" ref="viewBody"></div>
|
<div v-html="markdownHtml" class="view-body" id="viewBody" ref="viewBody"></div>
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
<el-tab-pane label="编辑" :disabled="!isEdit" v-if="isEdit">
|
<el-tab-pane label="编辑" :disabled="!isEdit" v-if="isEdit">
|
||||||
<div class="lineH25 view-body" contenteditable id="md-editor" @blur="emitMarkDown" v-html="markdown">
|
<div class="lineH25 view-body" contenteditable id="md-editor" @blur="emitMarkDown" v-html="markdown"></div>
|
||||||
<!-- <pre id="viewBody"></pre>-->
|
|
||||||
</div>
|
|
||||||
</el-tab-pane>
|
</el-tab-pane>
|
||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
@@ -47,6 +45,9 @@ export default {
|
|||||||
prdUrl: ``,
|
prdUrl: ``,
|
||||||
iframeSrc: window.location.origin,
|
iframeSrc: window.location.origin,
|
||||||
bboxList: [],
|
bboxList: [],
|
||||||
|
// 对照表
|
||||||
|
selectionTable: [], // 表格对照表 记录表格与image_path 关系
|
||||||
|
selectionImagePath: '', // 当前表格对应的图片路径
|
||||||
markdown: '',
|
markdown: '',
|
||||||
md,
|
md,
|
||||||
markdownHtml: '',
|
markdownHtml: '',
|
||||||
@@ -55,14 +56,36 @@ export default {
|
|||||||
tableActionButtons: [
|
tableActionButtons: [
|
||||||
{
|
{
|
||||||
label: '重新识别',
|
label: '重新识别',
|
||||||
icon: 'el-icon-edit',
|
icon: 'el-icon-view',
|
||||||
click: tableElement => {
|
click: tableElement => {
|
||||||
console.log('重新识别')
|
console.log('重新识别')
|
||||||
|
// 获取的 识别图片
|
||||||
|
this.selectionImagePath = ''
|
||||||
|
let chooseItem = this.findMatchingTable(tableElement)
|
||||||
|
|
||||||
|
if (!this.selectionImagePath) {
|
||||||
|
this.$message.error('未能识别当前表格图片')
|
||||||
|
return false
|
||||||
|
} else {
|
||||||
|
// this.finishedMiner = true
|
||||||
|
this.updateTableAttributes(tableElement, chooseItem)
|
||||||
|
let loading = this.$loading({
|
||||||
|
target: tableElement,
|
||||||
|
lockScroll: false,
|
||||||
|
// spinner: 'element-loading-spinner',
|
||||||
|
// background: 'rgba(0, 0, 0, 0.8)',
|
||||||
|
text: 'AI模型分析中....'
|
||||||
|
})
|
||||||
|
setTimeout(() => {
|
||||||
|
loading.close()
|
||||||
|
// tableElement.innerHTML = '<div>123</div>'
|
||||||
|
}, 3000)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: '编辑',
|
label: '编辑',
|
||||||
icon: 'el-icon-edit',
|
icon: 'el-icon-edit-outline',
|
||||||
click: tableElement => {
|
click: tableElement => {
|
||||||
this.copyTable = tableElement.innerHTML
|
this.copyTable = tableElement.innerHTML
|
||||||
tableElement.classList.remove('m-view')
|
tableElement.classList.remove('m-view')
|
||||||
@@ -76,7 +99,7 @@ export default {
|
|||||||
tableActionConfirm: [
|
tableActionConfirm: [
|
||||||
{
|
{
|
||||||
label: '返回',
|
label: '返回',
|
||||||
icon: 'el-icon-edit',
|
icon: 'el-icon-refresh-left',
|
||||||
click: tableElement => {
|
click: tableElement => {
|
||||||
tableElement.classList.add('m-view')
|
tableElement.classList.add('m-view')
|
||||||
tableElement.setAttribute('contenteditable', 'false')
|
tableElement.setAttribute('contenteditable', 'false')
|
||||||
@@ -122,31 +145,15 @@ export default {
|
|||||||
components: {},
|
components: {},
|
||||||
filters: {},
|
filters: {},
|
||||||
methods: {
|
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() {
|
viewHtmlModel() {
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
const mdHtml = document.getElementById('md-editor')
|
const mdHtml = document.getElementById('md-editor')
|
||||||
|
// 监听鼠标悬停事件,为表格元素添加浮层按钮
|
||||||
mdHtml.addEventListener('mouseover', e => {
|
mdHtml.addEventListener('mouseover', e => {
|
||||||
const tableElement = e.target.closest('table')
|
const tableElement = e.target.closest('table')
|
||||||
if (tableElement) {
|
if (tableElement) {
|
||||||
// 检查是否已经存在按钮容器
|
// 检查是否已经存在按钮容器,避免重复创建
|
||||||
let buttonContainer = tableElement.querySelector('.md-editor-setting')
|
let buttonContainer = tableElement.querySelector('.md-editor-setting')
|
||||||
if (!buttonContainer) {
|
if (!buttonContainer) {
|
||||||
buttonContainer = document.createElement('div')
|
buttonContainer = document.createElement('div')
|
||||||
@@ -155,6 +162,7 @@ export default {
|
|||||||
buttonContainer.style.zIndex = '9999'
|
buttonContainer.style.zIndex = '9999'
|
||||||
buttonContainer.className = 'md-editor-setting'
|
buttonContainer.className = 'md-editor-setting'
|
||||||
buttonContainer.setAttribute('contenteditable', 'false')
|
buttonContainer.setAttribute('contenteditable', 'false')
|
||||||
|
// 调用生成按钮的方法
|
||||||
this.generateButton(tableElement, buttonContainer)
|
this.generateButton(tableElement, buttonContainer)
|
||||||
// 设置按钮位置在表格正中间浮动
|
// 设置按钮位置在表格正中间浮动
|
||||||
const rect = tableElement.getBoundingClientRect()
|
const rect = tableElement.getBoundingClientRect()
|
||||||
@@ -165,6 +173,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 监听鼠标离开事件,移除浮层按钮
|
||||||
mdHtml.addEventListener('mouseout', e => {
|
mdHtml.addEventListener('mouseout', e => {
|
||||||
const tableElement = e.target.closest('table')
|
const tableElement = e.target.closest('table')
|
||||||
if (!tableElement || !tableElement.contains(e.relatedTarget)) {
|
if (!tableElement || !tableElement.contains(e.relatedTarget)) {
|
||||||
@@ -176,6 +185,115 @@ export default {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 封装 按钮生成事件
|
||||||
|
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 icon = document.createElement('i')
|
||||||
|
const button = document.createElement('button')
|
||||||
|
|
||||||
|
icon.className = `${buttons[i].icon} public-icon`
|
||||||
|
button.appendChild(icon)
|
||||||
|
// 悬浮提示
|
||||||
|
button.setAttribute('title', buttons[i].label)
|
||||||
|
button.className = 'el-button el-button--primary el-button--mini editor-button is-plain'
|
||||||
|
button.style.pointerEvents = 'auto'
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
buttons[i].click(tableElement)
|
||||||
|
})
|
||||||
|
buttonContainer.appendChild(button)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 查找匹配的表格
|
||||||
|
findMatchingTable(tableElement) {
|
||||||
|
let chooseItem = null
|
||||||
|
let pathId = tableElement.getAttribute('data-path-id')
|
||||||
|
if (pathId) {
|
||||||
|
let pathType = tableElement.getAttribute('data-path-type')
|
||||||
|
this.selectionImagePath = pathId + pathType
|
||||||
|
} else {
|
||||||
|
// 如果没有路径 ID,则通过表格内容匹配对照表
|
||||||
|
let tableText = this.getTableText(tableElement)
|
||||||
|
chooseItem = this.selectionTable.find(item => {
|
||||||
|
if (item.html) {
|
||||||
|
let itemText = this.getTableTextFromHtml(item.html)
|
||||||
|
return tableText === itemText
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
})
|
||||||
|
if (chooseItem) {
|
||||||
|
this.selectionImagePath = chooseItem.image_path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return chooseItem
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取表格文本
|
||||||
|
getTableText(tableElement) {
|
||||||
|
let stringTable = tableElement.innerHTML
|
||||||
|
let tbodyMatch = stringTable.match(/<tbody>([\s\S]*)<\/tbody>/)
|
||||||
|
if (tbodyMatch) {
|
||||||
|
let newTable = document.createElement('table')
|
||||||
|
newTable.innerHTML = tbodyMatch[1]
|
||||||
|
return newTable.innerText
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
|
||||||
|
// 从 HTML 中获取表格文本
|
||||||
|
getTableTextFromHtml(html) {
|
||||||
|
let tableMatch = html.match(/<table>([\s\S]*)<\/table>/)
|
||||||
|
if (tableMatch) {
|
||||||
|
let domTable = document.createElement('table')
|
||||||
|
domTable.innerHTML = tableMatch[1]
|
||||||
|
return domTable.innerText
|
||||||
|
}
|
||||||
|
return ''
|
||||||
|
},
|
||||||
|
|
||||||
|
// 更新表格属性
|
||||||
|
updateTableAttributes(tableElement, chooseItem) {
|
||||||
|
tableElement.setAttribute('data-path-id', this.selectionImagePath.replace(/\.[^/.]+$/, ''))
|
||||||
|
let fileType = this.selectionImagePath.match(/\.[^/.]+$/)[0]
|
||||||
|
tableElement.setAttribute('data-path-type', fileType)
|
||||||
|
},
|
||||||
|
|
||||||
|
linesMap(table) {
|
||||||
|
;(table.blocks ? table.blocks : []).map(lines => {
|
||||||
|
console.log(lines)
|
||||||
|
lines.lines.map(spans => {
|
||||||
|
spans.spans.map(span => {
|
||||||
|
this.selectionTable.push({ html: span.html, image_path: span.image_path })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
},
|
||||||
|
setTableSelection(selection) {
|
||||||
|
selection.map(item => {
|
||||||
|
if (item.preproc_blocks && item.preproc_blocks.length > 0) {
|
||||||
|
item.preproc_blocks.forEach(block => {
|
||||||
|
if (block.type === 'table') {
|
||||||
|
this.linesMap(block)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 处理丢弃块
|
||||||
|
if (item.discarded_blocks && item.discarded_blocks.length > 0) {
|
||||||
|
item.discarded_blocks.forEach(block => {
|
||||||
|
if (block.type === 'table') {
|
||||||
|
this.linesMap(block)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
|
||||||
// 导出
|
// 导出
|
||||||
emitMarkDown() {
|
emitMarkDown() {
|
||||||
let pre = document.getElementById('md-editor').innerText
|
let pre = document.getElementById('md-editor').innerText
|
||||||
@@ -225,6 +343,10 @@ export default {
|
|||||||
getPDFDetailBbox() {
|
getPDFDetailBbox() {
|
||||||
minerUBbox({ documentId: this.documentId }).then(res => {
|
minerUBbox({ documentId: this.documentId }).then(res => {
|
||||||
this.bboxList = this.formatJson(JSON.parse(JSON.stringify(res.content.content)))
|
this.bboxList = this.formatJson(JSON.parse(JSON.stringify(res.content.content)))
|
||||||
|
|
||||||
|
this.setTableSelection(JSON.parse(JSON.stringify(res.content.content)))
|
||||||
|
|
||||||
|
console.log(this.selectionTable, 'this.selectionTable')
|
||||||
// this.$refs.iframe 重新刷新iframe
|
// this.$refs.iframe 重新刷新iframe
|
||||||
this.$refs.iframe.contentWindow.location.reload()
|
this.$refs.iframe.contentWindow.location.reload()
|
||||||
this.getPDFDetailMarkDown()
|
this.getPDFDetailMarkDown()
|
||||||
@@ -245,7 +367,7 @@ export default {
|
|||||||
this.markdown = this.markdown
|
this.markdown = this.markdown
|
||||||
.replace(/<table/g, () => {
|
.replace(/<table/g, () => {
|
||||||
const uniqueId = `table-${this.tableIdCounter++}`
|
const uniqueId = `table-${this.tableIdCounter++}`
|
||||||
return `<table contenteditable='false' id="${uniqueId}" class="m-view"`
|
return `<table contenteditable='false' class="m-view"`
|
||||||
})
|
})
|
||||||
.replace(/<script/g, '< script')
|
.replace(/<script/g, '< script')
|
||||||
this.markdownHtml = this.md.render(this.markdown.replace(/class="m-view"/g, ''))
|
this.markdownHtml = this.md.render(this.markdown.replace(/class="m-view"/g, ''))
|
||||||
|
|||||||
@@ -14,16 +14,14 @@ import MavonEditor from './components/MavonEditor'
|
|||||||
import RenderMinerU from '@/components/RenderMinerU/index.vue'
|
import RenderMinerU from '@/components/RenderMinerU/index.vue'
|
||||||
import utils from '@/assets/js/common'
|
import utils from '@/assets/js/common'
|
||||||
// 生成的数据交互api
|
// 生成的数据交互api
|
||||||
import generatedApi from '@/api/generatedApi'
|
|
||||||
import generatedFormat from '@/assets/js/generatedFormat'
|
import generatedFormat from '@/assets/js/generatedFormat'
|
||||||
import generatedComponents from './generatedComponents'
|
import generatedComponents from './generatedComponents'
|
||||||
Vue.prototype.$generatedApi = generatedApi
|
|
||||||
Vue.prototype.$generatedFormat = generatedFormat
|
Vue.prototype.$generatedFormat = generatedFormat
|
||||||
Vue.prototype.$utils = utils
|
Vue.prototype.$utils = utils
|
||||||
Vue.prototype.$generatedDictList = generatedFormat.formatList
|
Vue.prototype.$generatedDictList = generatedFormat.formatList
|
||||||
// 注册过滤器
|
// 注册过滤器
|
||||||
Object.keys(Filters).forEach(k => Vue.filter(k, Filters[k]))
|
Object.keys(Filters).forEach(k => Vue.filter(k, Filters[k]))
|
||||||
for(let item in generatedComponents){
|
for (let item in generatedComponents) {
|
||||||
Vue.component(item, generatedComponents[item])
|
Vue.component(item, generatedComponents[item])
|
||||||
}
|
}
|
||||||
import '@/icons' // icon
|
import '@/icons' // icon
|
||||||
|
|||||||
@@ -50,9 +50,9 @@ export default {
|
|||||||
name: 'create',
|
name: 'create',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
visible: false,
|
visible: true,
|
||||||
active: 0,
|
active: 0,
|
||||||
documentId: '1361407835582337024'
|
documentId: '1362161577009188864'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
props: {},
|
props: {},
|
||||||
|
|||||||
Reference in New Issue
Block a user