import Vue from 'vue'; import JSZip from 'jszip'; import Common from '@/components/common/common' import Tiff from 'tiff.js'; var mediaUrl = Common.mediaUrl + 'articleImage/'; // var mediaUrl1 = 'https://submission.tmrjournals.com/public/articleImage/'; const fs = require('fs'); // 替换负号的方法 const replaceNegativeSign = function (text) { return text.replace(/^-(?=\d)/, "−"); }; // 首字母大写的方法 const capitalizeFirstLetter = function (text) { return text.replace(/^\s*([a-zA-Z])/, function (match, firstLetter) { return firstLetter.toUpperCase(); }); }; export default { decodeHtml(html) { var txt = document.createElement('textarea'); txt.innerHTML = html; return txt.value; }, //去掉最外层自定义的span标签 extractContentWithoutOuterSpan(cell) { var str = '' // 获取单元格的 HTML 内容 let htmlContent = cell.innerHTML.trim(); str = this.transformHtmlString(htmlContent) // 创建一个临时的 DOM 元素来解析 HTML const div = document.createElement('div'); div.innerHTML = htmlContent; // 检查是否有最外层的 标签,如果有,移除它 const firstChild = div.firstElementChild; if (firstChild && firstChild.tagName === 'SPAN') { // 移除最外层的 标签,并保留其内部内容 str = firstChild.innerHTML; } // 替换负号 str = replaceNegativeSign(str); // 首字母大写 str = capitalizeFirstLetter(str); // 添加蓝色标签 const regex = /\[(\d+(?:–\d+)?(?:, ?\d+(?:–\d+)?)*)\]/g; str = str.replace(//g, '').replace(/<\/blue>/g, ''); // 先去掉所有的 标签 if (regex.test(str)) { str = str.replace(regex, function (match) { // 提取出方括号中的内容,并进行匹配 const content = match.slice(1, match.length - 1); // 去掉方括号 // 判断是否符合条件,纯数字、逗号后有空格、连字符 if (/^\d+$/.test(content) || /, ?/.test(content) || /–/.test(content)) { return `${match}`; // 如果符合条件则加上蓝色标签 } return match; // 如果不符合条件,则保持原样 }); } // 如果没有 标签,直接返回原始 HTML 内容 return str; }, async extractPastedWordTablesToArrays(pastedHtml, callback) { try { // 创建临时 DOM 容器 const tempDiv = document.createElement("div"); tempDiv.innerHTML = pastedHtml; // 插入粘贴的 HTML 内容 // 获取表格 const tables = tempDiv.querySelectorAll("table"); if (tables.length === 0) { console.warn("未找到表格内容,请检查 HTML 结构"); callback([]); return; } const allTables = []; // 存储所有表格的二维数组 for (const table of tables) { const rows = table.querySelectorAll("tr"); const tableArray = []; // 当前表格的二维数组 // 存储合并单元格信息 const mergeMap = {}; rows.forEach((row, rowIndex) => { const cells = row.querySelectorAll("td, th"); const rowArray = []; let colIndex = 0; cells.forEach((cell) => { // 跳过被合并的单元格 while (mergeMap[`${rowIndex},${colIndex}`]) { colIndex++; } // 获取单元格内容,如果为空则设置为默认值 let cellText = cell.innerText.trim() || " "; // 处理空值 // 处理样式 if (cell.style.fontWeight === "bold") { cellText = `${cellText}`; } if (cell.style.fontStyle === "italic") { cellText = `${cellText}`; } if (cell.style.verticalAlign === "super") { cellText = `${cellText}`; } if (cell.style.verticalAlign === "sub") { cellText = `${cellText}`; } // 检查合并单元格属性 const colspan = parseInt(cell.getAttribute("colspan") || "1", 10); const rowspan = parseInt(cell.getAttribute("rowspan") || "1", 10); // 保存当前单元格信息 rowArray[colIndex] = { text: cellText, colspan: colspan, rowspan: rowspan, }; // 更新合并单元格信息 if (rowspan > 1 || colspan > 1) { for (let i = 0; i < rowspan; i++) { for (let j = 0; j < colspan; j++) { if (i === 0 && j === 0) continue; // 跳过起始单元格 mergeMap[`${rowIndex + i},${colIndex + j}`] = true; } } } colIndex++; // 移动到下一列 }); tableArray.push(rowArray); // 添加当前行到表格数组 }); allTables.push(tableArray); // 添加当前表格到所有表格数组 } console.log("解析后的表格数组:", allTables); callback(allTables); // 返回处理后的数组 } catch (error) { console.error("解析粘贴内容失败:", error); callback([]); } }, async extractWordTablesToArrays(file, callback) { const namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; try { const Zip = new JSZip(); const zip = await Zip.loadAsync(file); console.log("解压后的文件:", Object.keys(zip.files)); const documentFile = zip.file("word/document.xml"); if (!documentFile) { console.error("❌ 找不到 word/document.xml,无法解析 Word 文件"); return; } const documentXml = await documentFile.async("string"); const parser = new DOMParser(); const documentDoc = parser.parseFromString(documentXml, "application/xml"); console.log("解析的 XML 结构:", new XMLSerializer().serializeToString(documentDoc)); const numberingFile = zip.file("word/numbering.xml"); let numberingMap = {}; if (numberingFile) { const numberingXml = await numberingFile.async("string"); const numberingDoc = parser.parseFromString(numberingXml, "application/xml"); numberingMap = this.parseNumbering(numberingDoc); } else { console.warn("⚠️ word/numbering.xml 不存在,跳过编号解析"); } const tables = documentDoc.getElementsByTagNameNS(namespace, "tbl"); const allTables = []; if (!tables || tables.length === 0) { console.warn("未找到表格内容,请检查 XML 结构"); return []; } for (const table of tables) { const rows = table.getElementsByTagNameNS(namespace, "tr"); const tableArray = []; let rowSpanMap = []; for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { const row = rows[rowIndex]; const cells = row.getElementsByTagNameNS(namespace, "tc"); const rowArray = []; if (!rowSpanMap[rowIndex]) { rowSpanMap[rowIndex] = []; } let cellIndex = 0; for (let i = 0; i < cells.length; i++) { while (rowSpanMap[rowIndex][cellIndex]) { rowArray.push(null); cellIndex++; } const cell = cells[i]; let cellText = ""; const paragraphs = cell.getElementsByTagName("w:p"); const gridSpan = cell.getElementsByTagNameNS(namespace, "gridSpan")[0]; const vMerge = cell.getElementsByTagNameNS(namespace, "vMerge")[0]; var colspan = gridSpan ? parseInt(gridSpan.getAttribute("w:val"), 10) : 1; var rowspan = 1; if (vMerge) { if (vMerge.getAttribute("w:val") === "restart") { rowspan = 1; // 初始化 rowspan let nextRowIdx = rowIndex + 1; let maxRowspan = rows.length - rowIndex; // 确保 rowspan 不会超过剩余行数 while (nextRowIdx < rows.length) { const nextRowCells = rows[nextRowIdx].getElementsByTagNameNS(namespace, "tc"); // console.log(`🔍 检查下一行单元格 at row ${nextRowIdx}, col ${cellIndex}:`, nextRowCells); if (nextRowCells.length > cellIndex) { const nextCell = nextRowCells[cellIndex]; if (!nextCell) { // console.warn(`⚠️ nextCell 未定义 at row ${nextRowIdx}, col ${cellIndex}`); break; } const nextVMerge = nextCell.getElementsByTagNameNS(namespace, "vMerge")[0]; // console.log(`🔍 检查 nextVMerge at row ${nextRowIdx}, col ${cellIndex}:`, nextVMerge); // **如果 nextVMerge 为空,则不应继续增长 rowspan** if (!nextVMerge) { // console.log(`⚠️ nextVMerge 为空 at row ${nextRowIdx}, col ${cellIndex} - 停止扩展`); break; } // **解析 nextVMerge 的值** const vMergeVal = nextVMerge.getAttribute("w:val"); if (!vMergeVal || vMergeVal === "continue") { if (rowspan < maxRowspan) { // 限制 rowspan 最大值 rowspan++; // console.log(`✅ rowspan 扩展到: ${rowspan} (row: ${nextRowIdx}, col: ${cellIndex})`); nextRowIdx++; } else { // console.log(`⛔ 最大 rowspan 限制 ${rowspan},在 row ${nextRowIdx} 停止`); break; } } else if (vMergeVal === "restart") { // console.log(`⛔ 停止 rowspan 扩展 at row ${nextRowIdx}, 因为 w:val="restart"`); break; } else { // console.log(`⚠️ 未知 w:val="${vMergeVal}" at row ${nextRowIdx},停止合并`); break; } } else { // console.warn(`⚠️ Row ${nextRowIdx} 没有足够的列 cellIndex ${cellIndex}`); break; } } } else { continue; } } console.log('rowspan at line 265:', rowspan) const currentLevelNumbers = {}; for (const paragraph of paragraphs) { let listPrefix = ""; const numPr = paragraph.getElementsByTagName("w:numPr")[0]; if (numPr) { const numIdElement = numPr.getElementsByTagName("w:numId")[0]; const ilvlElement = numPr.getElementsByTagName("w:ilvl")[0]; if (numIdElement && ilvlElement) { const numId = numIdElement.getAttribute("w:val"); const ilvl = ilvlElement.getAttribute("w:val"); listPrefix = this.getListNumber(numId, ilvl, numberingMap, currentLevelNumbers); } } let paragraphText = listPrefix ? `${listPrefix} ` : ""; const runs = paragraph.getElementsByTagName("w:r"); for (const run of runs) { let textContent = ""; const texts = run.getElementsByTagName("w:t"); for (const text of texts) { textContent += text.textContent; } const rPr = run.getElementsByTagName("w:rPr")[0]; let formattedText = textContent; if (rPr) { const bold = rPr.getElementsByTagName("w:b").length > 0; const italic = rPr.getElementsByTagName("w:i").length > 0; const vertAlignElement = rPr.getElementsByTagName("w:vertAlign")[0]; if (bold) { formattedText = `${formattedText}`; } if (italic) { formattedText = `${formattedText}`; } if (vertAlignElement) { const vertAlign = vertAlignElement.getAttribute("w:val"); if (vertAlign === "superscript") { formattedText = `${formattedText}`; } else if (vertAlign === "subscript") { formattedText = `${formattedText}`; } } } formattedText = replaceNegativeSign(formattedText); formattedText = capitalizeFirstLetter(formattedText); const regex = /\[(\d+(?:–\d+)?(?:, ?\d+(?:–\d+)?)*)\]/g; formattedText = formattedText.replace(//g, '').replace(/<\/blue>/g, ''); if (regex.test(formattedText)) { formattedText = formattedText.replace(regex, function (match) { const content = match.slice(1, match.length - 1); if (/^\d+$/.test(content) || /, ?/.test(content) || /–/.test(content)) { return `${match}`; } return match; }); } console.log("After replacement:", formattedText); paragraphText += formattedText; } const breaks = paragraph.getElementsByTagName("w:br"); for (const br of breaks) { paragraphText += "
"; } cellText += paragraphText; console.log('cellText at line 366:', cellText) } rowArray.push({ text: cellText, colspan: colspan, rowspan: rowspan }); if (rowspan > 1) { for (let j = 1; j < rowspan; j++) { if (!rowSpanMap[rowIndex + j]) { rowSpanMap[rowIndex + j] = []; } rowSpanMap[rowIndex + j][cellIndex] = true; } } cellIndex++; } tableArray.push(rowArray.filter(item => item !== null)); } allTables.push(tableArray); } console.log("解析后的二维数组:", allTables); callback(allTables); } catch (error) { console.error("解析 Word 文件失败:", error); callback([]); } }, // async extractWordTablesToArrays(file, callback) { // const namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; // try { // const Zip = new JSZip(); // // 解压 ZIP // const zip = await Zip.loadAsync(file); // // **检查解压的文件列表** // console.log("解压后的文件:", Object.keys(zip.files)); // const documentFile = zip.file("word/document.xml"); // if (!documentFile) { // console.error("❌ 找不到 word/document.xml,无法解析 Word 文件"); // return; // } // const documentXml = await documentFile.async("string"); // const parser = new DOMParser(); // const documentDoc = parser.parseFromString(documentXml, "application/xml"); // // **打印 XML 结构以检查表格** // console.log("解析的 XML 结构:", new XMLSerializer().serializeToString(documentDoc)); // // **检查 word/numbering.xml 是否存在** // const numberingFile = zip.file("word/numbering.xml"); // let numberingMap = {}; // if (numberingFile) { // const numberingXml = await numberingFile.async("string"); // const numberingDoc = parser.parseFromString(numberingXml, "application/xml"); // numberingMap = parseNumbering(numberingDoc); // 解析编号信息 // } else { // console.warn("⚠️ word/numbering.xml 不存在,跳过编号解析"); // } // const tables = documentDoc.getElementsByTagNameNS(namespace, "tbl"); // const allTables = []; // 存储所有表格的二维数组 // if (!tables || tables.length === 0) { // console.warn("未找到表格内容,请检查 XML 结构"); // return []; // } // for (const table of tables) { // const prevParagraph = table.previousElementSibling; // if (prevParagraph) { // console.log(`表格前的段落: ${prevParagraph.textContent}`); // } // const rows = table.getElementsByTagNameNS(namespace, "tr"); // const tableArray = []; // 当前表格的二维数组 // for (const row of rows) { // const cells = row.getElementsByTagNameNS(namespace, "tc"); // const rowArray = []; // 当前行的数据 // // 存储已合并的单元格 // let colSpanInfo = []; // for (let i = 0; i < cells.length; i++) { // const cell = cells[i]; // let cellText = ""; // // const paragraphs = cell.getElementsByTagNameNS(namespace, "p"); // const paragraphs = cell.getElementsByTagName("w:p"); // // 检查合并单元格属性 // const gridSpan = cell.getElementsByTagNameNS(namespace, "gridSpan")[0]; // const vMerge = cell.getElementsByTagNameNS(namespace, "vMerge")[0]; // // 获取合并信息 // let colspan = gridSpan ? parseInt(gridSpan.getAttribute("w:val"), 10) : 1; // let rowspan = 1; // if (vMerge && vMerge.getAttribute("w:val") === "restart") { // rowspan = 4; // 假设合并两行 // } else if (vMerge && !vMerge.getAttribute("w:val")) { // continue; // 如果是合并单元格的继续部分,则跳过 // } // // 处理单元格内容 // // let cellText = ""; // // 为当前单元格初始化计数器 // const currentLevelNumbers = {}; // for (const paragraph of paragraphs) { // // 提取段落编号 // let listPrefix = ""; // const numPr = paragraph.getElementsByTagName("w:numPr")[0]; // if (numPr) { // console.log('numPr at line 54:', numPr) // const numIdElement = numPr.getElementsByTagName("w:numId")[0]; // console.log('numIdElement at line 56:', numIdElement) // const ilvlElement = numPr.getElementsByTagName("w:ilvl")[0]; // console.log('ilvlElement at line 58:', ilvlElement) // if (numIdElement && ilvlElement) { // const numId = numIdElement.getAttribute("w:val"); // const ilvl = ilvlElement.getAttribute("w:val"); // listPrefix = this.getListNumber(numId, ilvl, numberingMap, currentLevelNumbers); // } // } // // 初始化当前段落文本 // let paragraphText = listPrefix ? `${listPrefix} ` : ""; // const runs = paragraph.getElementsByTagName("w:r"); // for (const run of runs) { // let textContent = ""; // const texts = run.getElementsByTagName("w:t"); // for (const text of texts) { // textContent += text.textContent; // } // // 检查格式 // const rPr = run.getElementsByTagName("w:rPr")[0]; // let formattedText = textContent; // if (rPr) { // const bold = rPr.getElementsByTagName("w:b").length > 0; // const italic = rPr.getElementsByTagName("w:i").length > 0; // const vertAlignElement = rPr.getElementsByTagName("w:vertAlign")[0]; // if (bold) { // formattedText = `${formattedText}`; // } // if (italic) { // formattedText = `${formattedText}`; // } // if (vertAlignElement) { // const vertAlign = vertAlignElement.getAttribute("w:val"); // if (vertAlign === "superscript") { // formattedText = `${formattedText}`; // } else if (vertAlign === "subscript") { // formattedText = `${formattedText}`; // } // } // } // // 替换负号 // formattedText = replaceNegativeSign(formattedText); // // 首字母大写 // formattedText = capitalizeFirstLetter(formattedText); // // 添加蓝色标签 // const regex = /\[(\d+(?:–\d+)?(?:, ?\d+(?:–\d+)?)*)\]/g; // formattedText = formattedText.replace(//g, '').replace(/<\/blue>/g, ''); // 先去掉所有的 标签 // if (regex.test(formattedText)) { // formattedText = formattedText.replace(regex, function (match) { // // 提取出方括号中的内容,并进行匹配 // const content = match.slice(1, match.length - 1); // 去掉方括号 // // 判断是否符合条件,纯数字、逗号后有空格、连字符 // if (/^\d+$/.test(content) || /, ?/.test(content) || /–/.test(content)) { // return `${match}`; // 如果符合条件则加上蓝色标签 // } // return match; // 如果不符合条件,则保持原样 // }); // } // console.log("After replacement:", formattedText); // 调试:查看替换后的文本 // paragraphText += formattedText; // } // // 处理换行符 // const breaks = paragraph.getElementsByTagName("w:br"); // for (const br of breaks) { // paragraphText += "
"; // } // cellText += paragraphText; // 将段落文本添加到单元格文本 // } // // 更新合并单元格的信息 // if (colSpanInfo[i]) { // colspan = colSpanInfo[i].colspan; // } // // 保存当前单元格信息 // rowArray.push({ // text: cellText, // colspan: colspan, // rowspan: rowspan // }); // // 记录跨列合并 // if (colspan > 1) { // for (let j = 1; j < colspan; j++) { // colSpanInfo[i + j] = { colspan: 0 }; // 用 0 填充后续的列合并 // } // } // } // tableArray.push(rowArray); // 添加当前行到表格数组 // } // allTables.push(tableArray); // 添加当前表格到所有表格数组 // } // console.log("解析后的二维数组:", allTables); // callback(allTables); // 返回处理后的 HTML // } catch (error) { // console.error("解析 Word 文件失败:", error); // return []; // } // }, transformHtmlString(inputHtml) { // inputHtml = inputHtml.replace(/(<[^>]+) style="[^"]*"/g, '$1'); // 移除style属性 // inputHtml = inputHtml.replace(/(<[^>]+) class="[^"]*"/g, '$1'); // 移除class属性 inputHtml = inputHtml.replace(/<([a-zA-Z0-9]+)[^>]*>/g, '<$1>'); // 删除标签上的所有属性 // 2. 删除所有不需要的标签 (除 `strong`, `em`, `sub`, `sup`, `b`, `i` 外的所有标签) inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue))[^>]+>/g, ''); // 删除不需要的标签 // 3. 如果有 `` 和 `` 标签,去掉内部样式并保留内容 inputHtml = inputHtml.replace(/]*>/g, '').replace(/<\/span>/g, ''); // 去除span标签 inputHtml = inputHtml.replace(//g, '').replace(/<\/strong>/g, ''); // 将 `strong` 替换成 `b` inputHtml = inputHtml.replace(//g, '').replace(/<\/em>/g, ''); // 将 `em` 替换成 `i` // 4. 合并相同标签(如多个连续的 标签) inputHtml = inputHtml.replace(/(.*?)<\/b>\s*/g, '$1'); // 合并连续的 标签 inputHtml = inputHtml.replace(/(.*?)<\/i>\s*/g, '$1'); // 合并连续的 标签 return inputHtml; }, cleanAndParseWordContent(content) { // 1️⃣ 解析成

段落数组 let tempDiv = document.createElement('div'); tempDiv.innerHTML = content; // 解析 HTML 内容 let paragraphs = tempDiv.querySelectorAll("p"); // 选取所有

作为数据项 // 2️⃣ 将

内容转换为数组,并处理内容 let parsedData = Array.from(paragraphs).map(p => { let text = p.innerHTML.trim(); // 获取内容,去除两端空格 // 3️⃣ **正确移除 (Word 复制的无效标签)** text = text.replace(/<\/?o:p[^>]*>/g, ""); // 4️⃣ **移除所有 style="..."** text = text.replace(/\s*style="[^"]*"/gi, ""); // 5️⃣ **修正标签替换** text = text.replace(//gi, "").replace(/<\/strong>/gi, ""); text = text.replace(//gi, "").replace(/<\/em>/gi, ""); // 6️⃣ **移除空的 span、b、i 标签** text = text.replace(/\s*<\/span>/gi, ""); text = text.replace(/\s*<\/b>/gi, ""); text = text.replace(/\s*<\/i>/gi, ""); // 7️⃣ **确保不移除半个标签(修复匹配规则)** text = text.replace(/<[^\/>]+>\s*<\/[^>]+>/gi, match => { return match.trim() === "" ? "" : match; }); // 8️⃣ **返回最终内容** return text.trim() === "" ? "" : text; }); console.log(parsedData); // 输出数组,方便调试 return parsedData; } , parseTableToArray(tableString, callback) { const parser = new DOMParser(); const doc = parser.parseFromString(tableString, 'text/html'); const rows = doc.querySelectorAll('table tr'); // 获取所有的行() callback(Array.from(rows).map(row => { const cells = row.querySelectorAll('th, td'); // 获取每个行中的单元格(包括 和 ) return Array.from(cells).map(cell => ({ text: this.extractContentWithoutOuterSpan(cell), colspan: cell.getAttribute('colspan') ? parseInt(cell.getAttribute('colspan')) : 1, // 提取 colspan,默认值为 1 rowspan: cell.getAttribute('rowspan') ? parseInt(cell.getAttribute('rowspan')) : 1 // 提取 rowspan,默认值为 1 })); })); }, parseNumbering(numberingDoc) { const numberingMap = new Map(); if (!numberingDoc) return numberingMap; // 解析 const nums = numberingDoc.getElementsByTagName("w:num"); const abstractNums = numberingDoc.getElementsByTagName("w:abstractNum"); const abstractNumMap = new Map(); for (const abstractNum of abstractNums) { const abstractNumId = abstractNum.getAttribute("w:abstractNumId"); const levels = abstractNum.getElementsByTagName("w:lvl"); for (const level of levels) { const ilvl = level.getAttribute("w:ilvl"); const lvlText = level.getElementsByTagName("w:lvlText")[0] ? level.getElementsByTagName("w:lvlText")[0].getAttribute("w:val") : null; const numFmt = level.getElementsByTagName("w:numFmt")[0] ? level.getElementsByTagName("w:numFmt")[0].getAttribute("w:val") : null; if (lvlText && numFmt) { abstractNumMap.set(`${abstractNumId}_${ilvl}`, { lvlText, numFmt }); } } } for (const num of nums) { const numId = num.getAttribute("w:numId"); const abstractNumId = num.getElementsByTagName("w:abstractNumId")[0] ? num.getElementsByTagName("w:abstractNumId")[0].getAttribute("w:val") : null; if (abstractNumId) { numberingMap.set(numId, abstractNumMap.get(`${abstractNumId}_0`)); // 默认只取0级 } } return numberingMap; } , // 根据编号 ID 和级别动态生成列表前缀 getListNumber(numId, ilvl, numberingMap, currentLevelNumbers) { // 获取编号定义 const levelInfo = numberingMap.get(numId); if (!levelInfo) return ""; const { lvlText } = levelInfo[ilvl] || {}; if (!lvlText) return ""; // 初始化当前级别的计数 if (currentLevelNumbers[ilvl] == null) { currentLevelNumbers[ilvl] = 1; } else { currentLevelNumbers[ilvl]++; } // 替换 %1, %2 等占位符 return lvlText.replace(/%(\d+)/g, (_, level) => { const levelIndex = parseInt(level, 10) - 1; return currentLevelNumbers[levelIndex] || 1; }); }, async extractTablesFromWord(arrayBuffer, callback) { const zip = new JSZip(); try { let html = ""; const content = await zip.loadAsync(arrayBuffer); // 解压 word/document.xml const documentXml = await content.file("word/document.xml").async("string"); // 解析 XML const parser = new DOMParser(); const xmlDoc = parser.parseFromString(documentXml, "application/xml"); const numberingXml = await zip.file("word/numbering.xml").async("string"); let numberingDoc = null; if (numberingXml) { numberingDoc = parser.parseFromString(numberingXml, "application/xml"); } // 获取编号定义 const numberingMap = this.parseNumbering(numberingDoc); console.log('numberingMap at line 232:', numberingMap) // 提取表格 // 获取所有的段落 const paragraphs = xmlDoc.getElementsByTagName("w:p"); // 获取所有的表格 const tables = xmlDoc.getElementsByTagName("w:tbl"); // 找到表格前的段落 let previousParagraphs = []; let tableIndex = 0; // 遍历段落,找到第一个表格之前的段落 for (let i = 0; i < paragraphs.length; i++) { if (tableIndex < tables.length && paragraphs[i].nextSibling === tables[tableIndex]) { break; // 找到表格 } previousParagraphs.push(paragraphs[i]); } // 将前一段的内容转化为 HTML 或文本 const previousHtml = this.convertParagraphsToHtml(previousParagraphs); console.log('tables at line 17:', previousHtml) const tableHtml = this.convertTablesToHtml(tables, numberingMap); console.log('tableHtml at line 18:', tableHtml) // 更新 HTML 内容 this.htmlContent = tableHtml || "

未检测到表格内容。

"; const container = document.createElement("div"); container.innerHTML = tableHtml; callback(this.updateTableStyles(container), []); // 返回处理后的 HTML } catch (error) { console.error("文件解析失败:", error); callback("

文件解析失败,请检查文件格式。

"); } }, // 转换段落为 HTML convertParagraphsToHtml(paragraphs) { let html = ""; paragraphs.forEach(p => { let paragraphHtml = ""; // 处理段落中的所有子元素 const runs = p.getElementsByTagName("w:r"); // 获取段落中的所有文本运行(可能包含上标、下标等) Array.from(runs).forEach(run => { let text = ""; // 获取文本内容 const textNodes = run.getElementsByTagName("w:t"); Array.from(textNodes).forEach(t => { text += t.textContent || t.text; }); // 检查是否为上标或下标 const isSuperscript = run.getElementsByTagName("w:vertAlign")[0] ? run.getElementsByTagName("w:vertAlign")[0].getAttribute("w:val") === "superscript" : ""; const isSubscript = run.getElementsByTagName("w:vertAlign")[0] ? run.getElementsByTagName("w:vertAlign")[0].getAttribute("w:val") === "subscript" : ''; if (isSuperscript) { text = `${text}`; } else if (isSubscript) { text = `${text}`; } // 拼接到段落 HTML 中 paragraphHtml += text; }); // 将运行的文本拼接成完整的段落 html += `

${paragraphHtml}

`; }); return html; } , convertTablesToHtml(tables, numberingMap) { const namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main"; let html = ""; // 遍历所有表格 for (const table of tables) { html += ""; const rows = table.getElementsByTagNameNS(namespace, "tr"); for (const row of rows) { html += ""; const cells = row.getElementsByTagNameNS(namespace, "tc"); for (const cell of cells) { let cellContent = ""; const paragraphs = cell.getElementsByTagNameNS(namespace, "p"); // 为每个单元格维护一个独立的计数器 const currentLevelNumbers = {}; for (const paragraph of paragraphs) { let paragraphContent = ""; let listPrefix = ""; // 检查段落编号 const numPr = paragraph.getElementsByTagNameNS(namespace, "numPr")[0]; if (numPr) { const numIdElement = numPr.getElementsByTagNameNS(namespace, "numId")[0]; const ilvlElement = numPr.getElementsByTagNameNS(namespace, "ilvl")[0]; if (numIdElement && ilvlElement) { const numId = numIdElement.getAttribute("w:val"); const ilvl = ilvlElement.getAttribute("w:val"); listPrefix = this.getListNumber(numId, ilvl, numberingMap, currentLevelNumbers); } } // 添加编号前缀 paragraphContent += listPrefix ? `${listPrefix} ` : ""; // 提取段落文本 const texts = paragraph.getElementsByTagNameNS(namespace, "t"); for (const text of texts) { paragraphContent += text.textContent; } // 处理换行符 const breaks = paragraph.getElementsByTagNameNS(namespace, "br"); for (const br of breaks) { paragraphContent += "
"; } cellContent += `

${paragraphContent}

`; } // 检查合并单元格属性 const gridSpan = cell.getElementsByTagNameNS(namespace, "gridSpan")[0]; const colspan = gridSpan ? parseInt(gridSpan.getAttribute("w:val"), 10) : 1; const vMerge = cell.getElementsByTagNameNS(namespace, "vMerge")[0]; let rowspan = 1; if (vMerge && vMerge.getAttribute("w:val") === "restart") { rowspan = 2; // 假设合并两行 } else if (vMerge && !vMerge.getAttribute("w:val")) { continue; } html += ``; } html += ""; } html += "
${cellContent}

"; } return html; }, // 更新表格样式 // 更新表格样式(如果需要额外样式,添加在这里) getWordTablesHtml(tables, callback) { let combinedHtml = ''; // 遍历每一个表格 tables.forEach((table) => { let tableHtml = ` `; // 遍历行 table.forEach((row) => { tableHtml += ``; // 遍历单元格 row.forEach((cell) => { tableHtml += ` `; }); tableHtml += ``; }); tableHtml += `
${cell.text}
`; combinedHtml += tableHtml; }); // 创建容器元素 const container = document.createElement("div"); container.innerHTML = combinedHtml; // 调用回调函数并返回最终结果 callback(this.updateTableStyles(container)); }, getWordTablesThumbnails(tables, callback, imgWidth, imgHeight, scale) { let combinedHtml = `
`; // 遍历每个表格,生成缩略图 tables.forEach((table, index) => { var tableStr = ` ` table.forEach((row) => { tableStr += ``; // 遍历单元格 row.forEach((cell) => { tableStr += ` `; }); tableStr += ``; }); tableStr += `
${cell.text}
`; combinedHtml += `
${tableStr}
` this.createImageModal(index, `
${tableStr}
`, 'table', ''); }) combinedHtml += `
`; const container = document.createElement("div"); container.innerHTML = combinedHtml; callback(container.innerHTML); }, // 创建模态框显示 TIFF 图像 createImageModal(index, contentHtml, modalName, style) { const existingModal = document.getElementById(`${modalName}-modal-${index}`); if (existingModal) { existingModal.remove(); // 如果存在则删除 } const modal = document.createElement('div'); modal.id = `${modalName}-modal-${index}`; modal.style.cssText = ` display: none !important; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.9); z-index: 9999; `; const modalBox = document.createElement('div'); modalBox.style.cssText = ` width: 100%; height: 100%;overflow-y: auto; display: flex; align-items: center; justify-content: center;` const content = document.createElement('div'); content.style.cssText = ` max-width: 90%; max-height: 90%; background: transparent; padding: 0; box-sizing: border-box; ${style} `; content.innerHTML = contentHtml; modalBox.appendChild(content); modal.appendChild(modalBox); // 点击模态框关闭 modal.onclick = () => { modal.style.display = 'none'; }; document.body.appendChild(modal); }, // 显示模态框 // 生成缩略图和模态框 getWordImagesThumbnails(images, callback, imgWidth, imgHeight, scale) { let combinedHtml = `
`; // 遍历每个图片,生成缩略图 images.forEach((img, index) => { const parts = img.image.split('.'); const extension = parts[parts.length - 1].toLowerCase(); // 确保后缀小写 let thumbnailContent = ''; if (['jpg', 'jpeg', 'png'].includes(extension)) { thumbnailContent = `Image ${index}`; } else if (extension === 'tif') { thumbnailContent = ``; } else { thumbnailContent = `
${extension.toUpperCase()}
`; } const modalContent = (['jpg', 'jpeg', 'png'].includes(extension)) ? `Image ${index}` : ``; // 创建缩略图容器 combinedHtml += `
${thumbnailContent}
`; if (extension === 'tif' || extension === 'jpg' || extension === 'jpeg' || extension === 'png') { this.createImageModal(index, modalContent, 'img'); } // 创建模态框内容 // 在显示模态框时触发渲染 TIFF if (extension === 'tif') { window.requestAnimationFrame(() => { // 保持原比例 this.renderTiffImage(mediaUrl + img.image, `tiff-canvas-modal-${index}`, index, true); console.log('at line 827:', document.getElementsByClassName('thumbnailBox')) // 不保持原比例,强制缩放到指定大小 const targetWidth = imgWidth || document.getElementsByClassName('thumbnailBox')[0].offsetWidth; // 使用外层容器宽度 const targetHeight = imgHeight || document.getElementsByClassName('thumbnailBox')[0].offsetHeight; // 使用外层容器高度 this.renderTiffImage(mediaUrl + img.image, `tiff-canvas-${index}`, index, false, targetWidth, targetHeight); }); } }); combinedHtml += `
`; const container = document.createElement("div"); container.innerHTML = combinedHtml; callback(container.innerHTML); }, getTiffDataUrl(tiffUrl, callback) { const canvas = document.createElement('canvas-' + tiffUrl); const dpr = 1; const xhr = new XMLHttpRequest(); xhr.open('GET', tiffUrl, true); xhr.responseType = 'arraybuffer'; xhr.onload = () => { const arrayBuffer = xhr.response; const tiff = new Tiff({ buffer: arrayBuffer }); const image = tiff.toCanvas(); const originalWidth = image.width; const originalHeight = image.height; // 确定目标宽高 let imgWidth, imgHeight; imgWidth = originalWidth; imgHeight = originalHeight; // 设置 Canvas 的物理大小(考虑设备像素比率) canvas.width = imgWidth * dpr; canvas.height = imgHeight * dpr; canvas.style.width = `${imgWidth}px`; canvas.style.height = `${imgHeight}px`; const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr); // 清空 Canvas 并绘制图像 ctx.clearRect(0, 0, imgWidth, imgHeight); ctx.drawImage(image, 0, 0, imgWidth, imgHeight); var dataURL = canvas.toDataURL(); callback(dataURL); }; xhr.send(); }, renderTiffImage(tiffUrl, canvasId, callback, index, keepAspectRatio = true, targetWidth = null, targetHeight = null) { const canvas = document.getElementById(canvasId); if (!canvas) return; const dpr = window.devicePixelRatio || 1; const xhr = new XMLHttpRequest(); xhr.open('GET', tiffUrl, true); xhr.responseType = 'arraybuffer'; xhr.onload = () => { const arrayBuffer = xhr.response; const tiff = new Tiff({ buffer: arrayBuffer }); const image = tiff.toCanvas(); const originalWidth = image.width; const originalHeight = image.height; // 确定目标宽高 let imgWidth, imgHeight; if (keepAspectRatio) { // 保持原比例 imgWidth = originalWidth; imgHeight = originalHeight; } else { // 使用目标宽高,不考虑原比例 imgWidth = targetWidth || originalWidth; imgHeight = targetHeight || originalHeight; } // 设置 Canvas 的物理大小(考虑设备像素比率) canvas.width = imgWidth * dpr; canvas.height = imgHeight * dpr; canvas.style.width = `${imgWidth}px`; canvas.style.height = `${imgHeight}px`; const ctx = canvas.getContext('2d'); ctx.scale(dpr, dpr); // 清空 Canvas 并绘制图像 ctx.clearRect(0, 0, imgWidth, imgHeight); ctx.drawImage(image, 0, 0, imgWidth, imgHeight); callback(canvas.toDataURL()) }; xhr.send(); }, // 新增的调整 Canvas 显示比例的函数 //更新传入所有表格样式 updateTableStyles(container, type, setTopBottomBorder) { var typesettingType = type ? type : 1 const tables = container.querySelectorAll('table'); tables.forEach((table) => { table.setAttribute( 'style', `border: none; margin: 0 auto !important;border-collapse: collapse; word-break: keep-all !important;` ); const cells = table.querySelectorAll('td'); cells.forEach((td) => { if (/^-?\d+(\.\d+)?$/.test(td.textContent.trim())) { this.replaceNegativeSign(td); } // 检查当前 td 是否包含上下标 if (!this.containsSupOrSub(td)) { // 递归处理单元格内的所有子节点 td.childNodes.forEach((node) => this.capitalizeFirstLetter(node)); // 替换 标签为其内部文本 td.querySelectorAll('a').forEach((a) => a.replaceWith(document.createTextNode(a.textContent))); } const childElements = td.querySelectorAll('*'); // 遍历每个子元素 childElements.forEach((element) => { // 如果元素的文本内容匹配正则表达式 if (/\[\d+(?:,\d+)*\]/g.test(element.textContent)) { element.classList.add('color-highlight'); element.style.color = 'rgb(0,130,170)'; } element.style.wordBreak = 'keep-all'; element.style.textAlign = "justify"; // 设置两端对齐 element.style.verticalAlign = "top"; // 设置两端对齐 }); }); if (setTopBottomBorder) { const firstRowTdElements = container.querySelectorAll('tr:first-child td'); // 获取第一个 中的所有 元素 firstRowTdElements.forEach((td) => { const currentStyle = td.getAttribute('style'); if (currentStyle) { td.setAttribute( 'style', currentStyle + ';border-top:1.0000pt solid #000 !important;mso-border-top-alt:0.5000pt solid #000 !important;border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;' ); } else { td.setAttribute( 'style', 'border-top:1.0000pt solid #000 !important;mso-border-top-alt:0.5000pt solid #000 !important;border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;' ); } }); const firstRowTdElementsLast = container.querySelectorAll('tr:last-of-type td'); firstRowTdElementsLast.forEach((td) => { // 获取当前的 style 属性(如果有) const currentStyle = td.getAttribute('style'); // 如果已有 style 属性,则追加边框样式;如果没有 style 属性,则设置新的 style if (currentStyle) { td.setAttribute( 'style', currentStyle + ';border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;' ); } else { td.setAttribute( 'style', 'border-bottom:1.0000pt solid #000 !important;mso-border-bottom-alt:0.5000pt solid #000 !important;' ); } }); } }); return Array.from(tables).map((table) => table.outerHTML).join(''); }, // 将 XML 表格转换为 HTML // convertTableToHtml(table) { // let html = ''; // const rows = table.getElementsByTagName('w:tr'); // 表格行 // for (let row of rows) { // html += ''; // const cells = row.getElementsByTagName('w:tc'); // 表格单元格 // for (let cell of cells) { // // 解析列合并 // const gridSpan = cell.getElementsByTagName('w:gridSpan')[0]; // const colspan = gridSpan ? gridSpan.getAttribute('w:val') : 1; // // 解析行合并 // const vMerge = cell.getElementsByTagName('w:vMerge')[0]; // let rowspan = 1; // if (vMerge && vMerge.getAttribute('w:val') === 'restart') { // rowspan = 1; // 行合并起点 // } else if (vMerge) { // continue; // 跳过合并的单元格 // } // // 提取单元格内容 // let cellContent = ''; // const paragraphs = cell.getElementsByTagName('w:p'); // 单元格中的段落 // for (let paragraph of paragraphs) { // const textNodes = paragraph.getElementsByTagName('w:t'); // 文本节点 // for (let textNode of textNodes) { // cellContent += textNode.textContent; // } // } // html += ``; // } // html += ''; // } // html += '
${cellContent}
'; // return html; // }, parseListNumber(paragraph) { const numPr = paragraph.getElementsByTagName('w:numPr')[0]; if (numPr) { const ilvl = numPr.getElementsByTagName('w:ilvl')[0]; const numId = numPr.getElementsByTagName('w:numId')[0]; const level = ilvl ? ilvl.getAttribute('w:val') : 0; const listId = numId ? numId.getAttribute('w:val') : 0; return `${listId}.${level}`; // 返回序号 } return ''; // 无序号 }, // 提取 w:r 节点中的样式并转换为 CSS getStyleFromRun(run) { const styleNode = run.getElementsByTagName('w:rPr')[0]; let style = ''; if (styleNode) { // 加粗 if (styleNode.getElementsByTagName('w:b').length > 0) { style += 'font-weight: bold;'; } // 斜体 if (styleNode.getElementsByTagName('w:i').length > 0) { style += 'font-style: italic;'; } // 上标或下标 const vertAlign = styleNode.getElementsByTagName('w:vertAlign')[0]; if (vertAlign) { const alignVal = vertAlign.getAttribute('w:val'); if (alignVal === 'superscript') { style += 'vertical-align: super !important; font-size: smaller;'; } else if (alignVal === 'subscript') { style += 'vertical-align: sub !important; font-size: smaller;'; } } // 字体颜色 const colorNode = styleNode.getElementsByTagName('w:color')[0]; if (colorNode) { const colorVal = colorNode.getAttribute('w:val'); style += `color: #${colorVal};`; } } return style; }, replaceNegativeSign(node) { if (node.nodeType === Node.TEXT_NODE) { // 如果是文本节点,替换负号 node.nodeValue = node.nodeValue.replace(/^-(?=\d)/, '−'); } else if (node.nodeType === Node.ELEMENT_NODE) { this.applyToChildNodes(node, (child) => this.replaceNegativeSign(child)); } }, capitalizeFirstLetter(node) { if (node.nodeType === Node.TEXT_NODE) { // 如果是文本节点,只处理第一个非空字符 node.nodeValue = node.nodeValue.replace(/^\s*([a-zA-Z])/, (match, firstLetter) => firstLetter.toUpperCase()); } else if (node.nodeType === Node.ELEMENT_NODE) { this.applyToChildNodes(node, (child) => this.capitalizeFirstLetter(child)); } }, applyToChildNodes(node, fn) { if (node.nodeType === Node.ELEMENT_NODE) { node.childNodes.forEach(fn); } }, containsSupOrSub(element) { // 如果当前节点是元素节点 if (element.nodeType === 1) { // 如果是 标签,返回 true if (element.tagName === 'SUP' || element.tagName === 'SUB') { return true; } // 否则,递归检查子节点 return Array.from(element.childNodes).some((child) => this.containsSupOrSub(child)); } // 如果不是元素节点(如文本节点),返回 false return false; }, initEditorButton(vueInstance, ed) { ed.ui.registry.addMenuButton('customDropdown', { text: 'Set Title', // 下拉框标题 fetch: function (callback) { // 定义下拉框的内容 const items = [ { label: 'First level title', value: 1 }, { label: 'Secondary Title', value: 2 }, { label: 'Third level title', value: 3 } ]; const menuItems = items.map((item) => ({ type: 'menuitem', text: item.label, onAction: function () { var edSelection = ed.selection; const selectedNode = edSelection.getNode(); // 获取选中的节点 if (selectedNode) { // 向上查找最外层的 div let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; } // 如果找到的 div 节点存在 if (outerDiv) { const dataId = outerDiv.getAttribute('main-id'); vueInstance.$emit('onEditTitle', { mainId: dataId, value: item.value }); } } } })); callback(menuItems); } }); ed.ui.registry.addButton('addRow', { icon: 'duplicate-row', text: 'Add Row', // 下拉框标题 onAction: function () { var edSelection = ed.selection; const selectedNode = edSelection.getNode(); // 获取选中的节点 let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; } // 如果找到的 div 节点存在 if (outerDiv) { const dataId = outerDiv.getAttribute('main-id'); console.log('dataId at line 1258:', dataId) vueInstance.$emit('onAddRow', dataId); } } }); // 添加自定义菜单项 ed.ui.registry.addButton('Save', { icon: 'checkmark', text: 'Save', onAction: function () { var deleteButtons = document.querySelectorAll('.tox-tinymce-inline'); var edSelection = ed.selection; const selectedNode = edSelection.getNode(); // 获取选中的节点 let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; } // 如果找到的 div 节点存在 if (outerDiv) { const dataId = outerDiv.getAttribute('main-id'); var content; console.log('outerDiv at line 663:', outerDiv.innerHTML); content = outerDiv.innerHTML.replace(/<(?!\/?(img|b|i|sub|sup|span|strong|em |blue)\b)[^>]+>/g, ''); content = content.replace(/<([a-zA-Z]+)>\s*<\/\1>/g, ''); content = content.replace(/ /g, ' '); content = content.replace(/\s*style="[^"]*"/g, ''); var div = document.createElement('div'); div.innerHTML = content; // 将 HTML 字符串加载到 div 中 // 替换所有 var strongTags = div.getElementsByTagName('strong'); for (var i = 0; i < strongTags.length; i++) { var bTag = document.createElement('b'); bTag.innerHTML = strongTags[i].innerHTML; // 保留内容 strongTags[i].parentNode.replaceChild(bTag, strongTags[i]); } // 替换所有 var emTags = div.getElementsByTagName('em'); for (var i = 0; i < emTags.length; i++) { var iTag = document.createElement('i'); iTag.innerHTML = emTags[i].innerHTML; // 保留内容 emTags[i].parentNode.replaceChild(iTag, emTags[i]); } content = div.innerHTML; vueInstance.$emit('saveContent', content, dataId); } } }); ed.ui.registry.addButton('level1', { // icon: 'highlight-bg-color', text: 'First level title', onAction: function () { var edSelection = ed.selection; const selectedNode = edSelection.getNode(); // 获取选中的节点 let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; } // 如果找到的 div 节点存在 if (outerDiv) { const dataId = outerDiv.getAttribute('main-id'); console.log('dataId at line 1258:', dataId) vueInstance.$emit('onEditTitle', { mainId: dataId, value: 1 }); } } }); ed.ui.registry.addButton('level2', { // icon: 'highlight-bg-color', text: 'Second level Title', onAction: function () { var edSelection = ed.selection; const selectedNode = edSelection.getNode(); // 获取选中的节点 let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; } // 如果找到的 div 节点存在 if (outerDiv) { const dataId = outerDiv.getAttribute('main-id'); console.log('dataId at line 1258:', dataId) vueInstance.$emit('onEditTitle', { mainId: dataId, value: 2 }); } } }); ed.ui.registry.addButton('level3', { // icon: 'highlight-bg-color', text: 'Third level title', onAction: function () { var edSelection = ed.selection; const selectedNode = edSelection.getNode(); // 获取选中的节点 let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; } // 如果找到的 div 节点存在 if (outerDiv) { const dataId = outerDiv.getAttribute('main-id'); console.log('dataId at line 1258:', dataId) vueInstance.$emit('onEditTitle', { mainId: dataId, value: 3 }); } } }); ed.ui.registry.addButton('Edit', { icon: 'highlight-bg-color', text: 'Edit', onAction: function () { var edSelection = ed.selection; const selectedNode = edSelection.getNode(); // 获取选中的节点 let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; } // 如果找到的 div 节点存在 if (outerDiv) { const dataId = outerDiv.getAttribute('main-id'); console.log('dataId at line 1258:', dataId) vueInstance.$emit('onEdit', dataId); } } }); ed.ui.registry.addButton('commentAdd', { icon: 'comment-add', text: 'Comment Add', onAction: function () { var edSelection = ed.selection; const selectedNode = edSelection.getNode(); // 获取选中的节点 if (selectedNode) { // 向上查找最外层的 div let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; } // 如果找到的 div 节点存在 if (outerDiv) { const dataId = outerDiv.getAttribute('main-id'); const type = outerDiv.getAttribute('type'); console.log('type:', type); // 获取选中的内容(HTML格式) let selectedContent = edSelection.getContent({ format: 'html' }); // 创建一个临时容器来处理 HTML const tempDiv = document.createElement('div'); tempDiv.innerHTML = selectedContent; // 检查是否包含 标签 const hasImage = tempDiv.querySelector('img') !== null; if (hasImage) { vueInstance.$message.error(vueInstance.$t('commonTable.selectComment')); return; // 如果包含图片,停止处理 } // 获取清理后的纯文本内容 let selectedText = tempDiv.innerText.trim(); // 使用 trim() 清理前后的空格 // 处理文本中的多余空格:替换多个连续空格为一个空格 selectedText = selectedText.replace(/\s+/g, ' '); // 确保保留的标签 const allowedTags = ['sup', 'sub', 'strong', 'em', 'b', 'i', 'blue', 'tr', 'td']; // 遍历选中的节点并保留需要的标签 function preserveTags(node) { if (node.nodeType === 3) { // 文本节点 return node.nodeValue; } if (node.nodeType === 1 && allowedTags.includes(node.nodeName.toLowerCase())) { return node.outerHTML; // 保留整个标签 } return ''; } let preservedContent = ''; Array.from(tempDiv.childNodes).forEach((childNode) => { preservedContent += preserveTags(childNode); }); // 检查选中的内容是否已经包含嵌套批注 const containsPositionRemark = tempDiv.querySelector('.positionRemarkIndex'); if (containsPositionRemark) { vueInstance.$message.error(vueInstance.$t('commonTable.alreadyCommented')); return; // 如果已有嵌套批注,停止处理 } // 如果内容不为空,发送批注请求 if (type == 0) { if (selectedText !== '') { vueInstance.$emit('onAddComment', { mainId: dataId, label: preservedContent // 发送保留标签的内容 }); } else { vueInstance.$message.error(vueInstance.$t('commonTable.selectComment')); } } else if (type == 1) { vueInstance.$emit('onAddComment', { mainId: dataId, label: preservedContent // 发送保留标签的内容 }); } else if (type == 2) { vueInstance.$emit('onAddComment', { mainId: dataId, label: preservedContent // 发送保留标签的内容 }); } } } } }); ed.ui.registry.addButton('delete', { icon: 'remove', text: 'Delete', onAction: function () { var edSelection = ed.selection; const selectedNode = edSelection.getNode(); // 获取选中的节点 if (selectedNode) { // 向上查找最外层的 div let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; } // 如果找到的 div 节点存在 if (outerDiv) { const dataId = outerDiv.getAttribute('main-id'); vueInstance.$emit('onDelete', dataId); } } } }); // 定义自定义按钮 ed.ui.registry.addButton('clearButton', { text: 'Empty', onAction: () => { // 插入自定义表格到编辑器中 ed.setContent(''); } }); ed.ui.registry.addButton('customBlue', { text: 'Blue', // 按钮文本 className: 'custom-button-blue', // 添加自定义类 // shortcut: "Ctrl+J", onAction: function () { // 在选中的文本周围包裹 标签 var selectedText = ed.selection.getContent(); console.log('selectedText at line 529:', selectedText); if (selectedText) { var wrappedText = `${selectedText}`; ed.selection.setContent(wrappedText); } else { this.$message.error('请选择要添加蓝色的文本'); } } }); ed.ui.registry.addButton('LateX', { text: 'LateX', // 按钮文本 className: 'custom-button-blue', // 添加自定义类 // shortcut: "Ctrl+J", onAction: function () { // 在选中的文本周围包裹 标签 // var selectedText = ed.selection.getContent(); // console.log('selectedText at line 529:', selectedText); // if (selectedText) { // var wrappedText = `${selectedText}`; // ed.selection.setContent(wrappedText); // } else { // this.$message.error('请选择要添加蓝色的文本'); // } } }); ed.ui.registry.addButton('myuppercase', { text: 'A', // 按钮文本 onAction: function () { // 在选中的文本周围包裹 标签 var selectedText = ed.selection.getContent({ format: 'html' }); // 确保选中的文本是单个单词,包括空格 if (selectedText.trim() && /^[\s\w]+$/.test(selectedText)) { // 使用正则将选中的文本中的第一个字母大写 var capitalizedText = selectedText.replace(/(^|\s)([a-zA-Z])/g, function (match, p1, p2) { return p1 + p2.toUpperCase(); }); // 替换选中的文本,保持空格和其他内容 ed.selection.setContent(capitalizedText); } else { vueInstance.$message.error(vueInstance.$t('commonTable.selectWord')); } } }); ed.ui.registry.addButton('myuppercase', { text: 'A', // 按钮文本 onAction: function () { // 在选中的文本周围包裹 标签 var selectedText = ed.selection.getContent({ format: 'html' }); // 确保选中的文本是单个单词,包括空格 if (selectedText.trim() && /^[\s\w]+$/.test(selectedText)) { // 使用正则将选中的文本中的第一个字母大写 var capitalizedText = selectedText.replace(/(^|\s)([a-zA-Z])/g, function (match, p1, p2) { return p1 + p2.toUpperCase(); }); // 替换选中的文本,保持空格和其他内容 ed.selection.setContent(capitalizedText); } else { vueInstance.$message.error(vueInstance.$t('commonTable.selectWord')); } } }); ed.ui.registry.addButton('myuppercasea', { text: 'a', // 按钮文本(小写字母) onAction: function () { // 获取选中的文本,保留 HTML 格式 var selectedText = ed.selection.getContent({ format: 'html' }); // 确保选中的文本是单个有效的单词,包括空格 if (selectedText.trim() && /^[\s\w]+$/.test(selectedText)) { // 使用正则将选中的文本中的第一个字母转换为小写 var lowercasedText = selectedText.replace(/(^|\s)([a-zA-Z])/g, function (match, p1, p2) { return p1 + p2.toLowerCase(); }); // 替换选中的文本,保持空格和其他内容 ed.selection.setContent(lowercasedText); } else { vueInstance.$message.error(vueInstance.$t('commonTable.selectWord')); } } }); ed.ui.registry.addButton('Line', { text: '−', // 按钮文本 onAction: function () { // 插入 `−` 符号到当前光标位置 ed.insertContent('−'); } }); ed.ui.registry.addButton('removeBlue', { text: 'Blue', // 按钮文本 onAction: function () { const range = ed.selection.getRng(); // 获取选区范围 let startNode = range.startContainer; // 获取选区起始节点 let closestBlue = null; // 向上查找最近的 标签 while (startNode) { if (startNode.nodeName && startNode.nodeName.toLowerCase() === 'blue') { closestBlue = startNode; break; } startNode = startNode.parentNode; } if (closestBlue) { // 如果找到最近的 标签,移除它的外层标签,但保留内容 const parent = closestBlue.parentNode; while (closestBlue.firstChild) { parent.insertBefore(closestBlue.firstChild, closestBlue); } parent.removeChild(closestBlue); } else { // 未找到 标签,仅移除选中的 标签内容 const selectedContent = ed.selection.getContent({ format: 'html' }); // 使用正则表达式移除选区中的 标签 const cleanedContent = selectedContent .replace(/]*>/g, '') // 删除起始标签 .replace(/<\/blue>/g, ''); // 删除结束标签 // 更新选中的内容 ed.selection.setContent(cleanedContent); } } }); }, inTinymceButtonClass() { setTimeout(function () { // 使用 querySelectorAll 获取所有符合条件的按钮 const buttons = [ { selector: '.tox-tbtn[data-mce-name="commentadd"]', className: 'tinymce-custom-button-commentadd' }, { selector: '.tox-tbtn[data-mce-name="addrow"]', className: 'tinymce-custom-button-addrow' }, { selector: '.tox-tbtn[data-mce-name="delete"]', className: 'tinymce-custom-button-delete' }, { selector: '.tox-tbtn[data-mce-name="edit"]', className: 'tinymce-custom-button-edit' }, { selector: '.tox-tbtn[data-mce-name="save"]', className: 'tinymce-custom-button-save' }, { selector: '.tox-tbtn[data-mce-name="customblue"]', className: 'tinymce-custom-button-blue' }, { selector: '.tox-tbtn[data-mce-name="removeblue"]', className: 'tinymce-custom-button-removeblue' } ]; // 遍历每个按钮并为每个按钮添加类 buttons.forEach(item => { const buttonElements = document.querySelectorAll(item.selector); buttonElements.forEach(button => { if (!button.classList.contains(item.className)) { // 防止重复添加 button.classList.add(item.className); } }); }); }, 100); // 延迟执行,确保按钮渲染完成 } // 通用递归方法 }; export { mediaUrl };