|
|
@ -266,6 +266,12 @@ |
|
|
|
{ field: 'comparedText', title: '第二段原文', type: 'markdown' }, |
|
|
|
{ field: 'modificationDisplay', title: '相似情况', type: 'markdown' }, |
|
|
|
], |
|
|
|
|
|
|
|
"allCheckRepeatText": [ |
|
|
|
{ field: 'originalText', title: '第一段原文', type: 'markdown' }, |
|
|
|
{ field: 'comparedText', title: '第二段原文', type: 'markdown' }, |
|
|
|
{ field: 'modificationDisplay', title: '相似情况', type: 'markdown' }, |
|
|
|
], |
|
|
|
"checkDocumentError": [ |
|
|
|
{ field: 'originalText', title: '原文', type: 'markdown' }, |
|
|
|
{ field: 'modifiedContent', title: '修改建议', type: 'markdown' }, |
|
|
@ -651,9 +657,10 @@ |
|
|
|
|
|
|
|
const handlePageSelect = async (page: number) => { |
|
|
|
currentPage.value = page; |
|
|
|
await renderPage(page, highlightText.value!); |
|
|
|
// 使用清除了**标记的文本进行高亮 |
|
|
|
await renderPage(page, highlightText.value); |
|
|
|
showPageSelectModal.value = false; |
|
|
|
// message.success(`已定位到第${page}页`); |
|
|
|
message.success(`已定位到第${page}页`); |
|
|
|
}; |
|
|
|
|
|
|
|
const handlePageModalClose = () => { |
|
|
@ -662,72 +669,117 @@ |
|
|
|
|
|
|
|
const locateByText = async (text: string) => { |
|
|
|
if (!pdfDoc || !text) return; |
|
|
|
|
|
|
|
// 去除Markdown格式中的**标记 |
|
|
|
const cleanText = text.replace(/\*\*/g, ''); |
|
|
|
|
|
|
|
const numPages = pdfDoc.numPages; |
|
|
|
const foundPages: number[] = []; |
|
|
|
|
|
|
|
// 搜索所有页面 |
|
|
|
for (let i = 1; i <= numPages; i++) { |
|
|
|
const page = await pdfDoc.getPage(i); |
|
|
|
const content = await page.getTextContent(); |
|
|
|
const pageText = content.items.map((item: any) => item.str).join(''); |
|
|
|
if (pageText.replace(/\s/g, '').includes(text.replace(/\s/g, ''))) { |
|
|
|
|
|
|
|
// 去除空格后比较文本 |
|
|
|
// 标准化文本,移除所有非文本字符 |
|
|
|
const normalizeText = (text: string) => { |
|
|
|
return text |
|
|
|
.replace(/[\r\n\t\f\v]/g, '') // 移除所有换行和不可见字符 |
|
|
|
.replace(/[^\u4e00-\u9fa5a-zA-Z0-9]/g, '') // 只保留中文、英文和数字 |
|
|
|
.toLowerCase(); // 转换为小写以进行不区分大小写的比较 |
|
|
|
}; |
|
|
|
|
|
|
|
const normalizedPageText = normalizeText(pageText); |
|
|
|
const normalizedSearchText = normalizeText(cleanText); |
|
|
|
|
|
|
|
if (normalizedPageText.includes(normalizedSearchText)) { |
|
|
|
foundPages.push(i); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (foundPages.length === 0) { |
|
|
|
message.warning('未在PDF中找到该文本'); |
|
|
|
return; |
|
|
|
} |
|
|
|
highlightText.value = text; // 记录要高亮的文本 |
|
|
|
|
|
|
|
// 保存要高亮的文本(去除**标记后的) |
|
|
|
highlightText.value = cleanText; |
|
|
|
|
|
|
|
// 如果只找到一个页面,直接跳转并高亮 |
|
|
|
if (foundPages.length === 1) { |
|
|
|
currentPage.value = foundPages[0]; |
|
|
|
await renderPage(foundPages[0], text); |
|
|
|
// message.success(`已定位到第${foundPages[0]}页`); |
|
|
|
await renderPage(foundPages[0], cleanText); |
|
|
|
message.success(`已定位到第${foundPages[0]}页`); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 如果找到多个页面,弹出选择对话框 |
|
|
|
matchedPages.value = foundPages; |
|
|
|
showPageSelectModal.value = true; |
|
|
|
}; |
|
|
|
|
|
|
|
// 高亮整段文本 |
|
|
|
async function highlightTextOnPage(page, ctx, viewport, targetText) { |
|
|
|
if (!targetText) return; |
|
|
|
|
|
|
|
// 确保去除**标记 |
|
|
|
const cleanTargetText = targetText.replace(/\*\*/g, ''); |
|
|
|
|
|
|
|
const textContent = await page.getTextContent(); |
|
|
|
const items = textContent.items as any[]; |
|
|
|
const allText = items.map(i => i.str).join('').replace(/\s/g, ''); |
|
|
|
const target = targetText.replace(/\s/g, ''); |
|
|
|
const startIdx = allText.indexOf(target); |
|
|
|
|
|
|
|
// 连接所有文本项,但不移除空格,以保持更好的匹配精度 |
|
|
|
const pageTextWithSpaces = items.map(i => i.str).join(''); |
|
|
|
const pageText = pageTextWithSpaces.replace(/\s/g, ''); |
|
|
|
const target = cleanTargetText.replace(/\s/g, ''); |
|
|
|
|
|
|
|
// 尝试在页面文本中查找目标文本 |
|
|
|
const startIdx = pageText.indexOf(target); |
|
|
|
if (startIdx === -1) return; |
|
|
|
|
|
|
|
|
|
|
|
// 反查属于哪些item |
|
|
|
let charCount = 0; |
|
|
|
let highlightItems: any[] = []; |
|
|
|
let highlightStarted = false; |
|
|
|
let highlightLength = 0; |
|
|
|
|
|
|
|
for (let item of items) { |
|
|
|
if (!highlightStarted && charCount + item.str.length > startIdx) { |
|
|
|
const itemTextNoSpace = item.str.replace(/\s/g, ''); |
|
|
|
if (!highlightStarted && charCount + itemTextNoSpace.length > startIdx) { |
|
|
|
highlightStarted = true; |
|
|
|
} |
|
|
|
if (highlightStarted && highlightLength < targetText.length) { |
|
|
|
|
|
|
|
if (highlightStarted && highlightLength < target.length) { |
|
|
|
highlightItems.push(item); |
|
|
|
highlightLength += item.str.length; |
|
|
|
if (highlightLength >= targetText.length) break; |
|
|
|
highlightLength += itemTextNoSpace.length; |
|
|
|
if (highlightLength >= target.length) break; |
|
|
|
} |
|
|
|
charCount += item.str.length; |
|
|
|
|
|
|
|
charCount += itemTextNoSpace.length; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
// 绘制高亮 |
|
|
|
ctx.save(); |
|
|
|
ctx.globalAlpha = 0.4; |
|
|
|
ctx.fillStyle = '#ffd54f'; |
|
|
|
|
|
|
|
for (let item of highlightItems) { |
|
|
|
const [a, b, c, d, e, f] = item.transform; |
|
|
|
// 计算文本块左上角坐标 |
|
|
|
const x = e; |
|
|
|
const y = f; |
|
|
|
const transform = item.transform; |
|
|
|
const x = transform[4]; // e |
|
|
|
const y = transform[5]; // f |
|
|
|
|
|
|
|
// 通过viewport变换 |
|
|
|
const pt = viewport.convertToViewportPoint(x, y); |
|
|
|
|
|
|
|
// pdf.js的y是基线,需调整 |
|
|
|
const height = item.height || item.fontSize || 10; |
|
|
|
ctx.fillRect(pt[0], pt[1] - height, item.width, height); |
|
|
|
const width = item.width || (item.str.length * (item.fontSize || 10) * 0.6); |
|
|
|
|
|
|
|
ctx.fillRect(pt[0], pt[1] - height, width, height + 2); // 稍微扩大高亮区域以便更容易看见 |
|
|
|
} |
|
|
|
|
|
|
|
ctx.restore(); |
|
|
|
} |
|
|
|
</script> |
|
|
|