Files
tougao_web/src/common/js/commonJS.js
2025-03-18 11:19:25 +08:00

1994 lines
84 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;
// 检查是否有最外层的 <span> 标签,如果有,移除它
const firstChild = div.firstElementChild;
if (firstChild && firstChild.tagName === 'SPAN') {
// 移除最外层的 <span> 标签,并保留其内部内容
str = firstChild.innerHTML;
}
// 替换负号
str = replaceNegativeSign(str);
// 首字母大写
str = capitalizeFirstLetter(str);
// 添加蓝色标签
const regex = /\[(\d+(?:\d+)?(?:, ?\d+(?:\d+)?)*)\]/g;
str = str.replace(/<blue>/g, '').replace(/<\/blue>/g, ''); // 先去掉所有的 <blue> 标签
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 `<blue>${match}</blue>`; // 如果符合条件则加上蓝色标签
}
return match; // 如果不符合条件,则保持原样
});
}
// 如果没有 <span> 标签,直接返回原始 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() || "&nbsp;"; // 处理空值
// 处理样式
if (cell.style.fontWeight === "bold") {
cellText = `<b>${cellText}</b>`;
}
if (cell.style.fontStyle === "italic") {
cellText = `<i>${cellText}</i>`;
}
if (cell.style.verticalAlign === "super") {
cellText = `<sup>${cellText}</sup>`;
}
if (cell.style.verticalAlign === "sub") {
cellText = `<sub>${cellText}</sub>`;
}
// 检查合并单元格属性
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 = `<b>${formattedText}</b>`;
}
if (italic) {
formattedText = `<i>${formattedText}</i>`;
}
if (vertAlignElement) {
const vertAlign = vertAlignElement.getAttribute("w:val");
if (vertAlign === "superscript") {
formattedText = `<sup>${formattedText}</sup>`;
} else if (vertAlign === "subscript") {
formattedText = `<sub>${formattedText}</sub>`;
}
}
}
formattedText = replaceNegativeSign(formattedText);
formattedText = capitalizeFirstLetter(formattedText);
const regex = /\[(\d+(?:\d+)?(?:, ?\d+(?:\d+)?)*)\]/g;
formattedText = formattedText.replace(/<blue>/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 `<blue>${match}</blue>`;
}
return match;
});
}
console.log("After replacement:", formattedText);
paragraphText += formattedText;
}
const breaks = paragraph.getElementsByTagName("w:br");
for (const br of breaks) {
paragraphText += "<br/>";
}
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 = `<b>${formattedText}</b>`;
// }
// if (italic) {
// formattedText = `<i>${formattedText}</i>`;
// }
// if (vertAlignElement) {
// const vertAlign = vertAlignElement.getAttribute("w:val");
// if (vertAlign === "superscript") {
// formattedText = `<sup>${formattedText}</sup>`;
// } else if (vertAlign === "subscript") {
// formattedText = `<sub>${formattedText}</sub>`;
// }
// }
// }
// // 替换负号
// formattedText = replaceNegativeSign(formattedText);
// // 首字母大写
// formattedText = capitalizeFirstLetter(formattedText);
// // 添加蓝色标签
// const regex = /\[(\d+(?:\d+)?(?:, ?\d+(?:\d+)?)*)\]/g;
// formattedText = formattedText.replace(/<blue>/g, '').replace(/<\/blue>/g, ''); // 先去掉所有的 <blue> 标签
// 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 `<blue>${match}</blue>`; // 如果符合条件则加上蓝色标签
// }
// return match; // 如果不符合条件,则保持原样
// });
// }
// console.log("After replacement:", formattedText); // 调试:查看替换后的文本
// paragraphText += formattedText;
// }
// // 处理换行符
// const breaks = paragraph.getElementsByTagName("w:br");
// for (const br of breaks) {
// paragraphText += "<br>";
// }
// 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. 如果有 `<strong>` 和 `<em>` 标签,去掉内部样式并保留内容
inputHtml = inputHtml.replace(/<span[^>]*>/g, '').replace(/<\/span>/g, ''); // 去除span标签
inputHtml = inputHtml.replace(/<strong>/g, '<b>').replace(/<\/strong>/g, '</b>'); // 将 `strong` 替换成 `b`
inputHtml = inputHtml.replace(/<em>/g, '<i>').replace(/<\/em>/g, '</i>'); // 将 `em` 替换成 `i`
// 4. 合并相同标签(如多个连续的 <b> 标签)
inputHtml = inputHtml.replace(/<b>(.*?)<\/b>\s*<b>/g, '<b>$1'); // 合并连续的 <b> 标签
inputHtml = inputHtml.replace(/<i>(.*?)<\/i>\s*<i>/g, '<i>$1'); // 合并连续的 <i> 标签
return inputHtml;
},
cleanAndParseWordContent(content) {
// 1⃣ 解析成 <p> 段落数组
let tempDiv = document.createElement('div');
tempDiv.innerHTML = content; // 解析 HTML 内容
let paragraphs = tempDiv.querySelectorAll("p"); // 选取所有 <p> 作为数据项
// 2⃣ 将 <p> 内容转换为数组,并处理内容
let parsedData = Array.from(paragraphs).map(p => {
let text = p.innerHTML.trim(); // 获取内容,去除两端空格
// 3⃣ **正确移除 <o:p>Word 复制的无效标签)**
text = text.replace(/<\/?o:p[^>]*>/g, "");
// 4⃣ **移除所有 style="..."**
text = text.replace(/\s*style="[^"]*"/gi, "");
// 5⃣ **修正标签替换**
text = text.replace(/<strong>/gi, "<b>").replace(/<\/strong>/gi, "</b>");
text = text.replace(/<em>/gi, "<i>").replace(/<\/em>/gi, "</i>");
// 6⃣ **移除空的 span、b、i 标签**
text = text.replace(/<span>\s*<\/span>/gi, "");
text = text.replace(/<b>\s*<\/b>/gi, "");
text = text.replace(/<i>\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'); // 获取所有的行(<tr>
callback(Array.from(rows).map(row => {
const cells = row.querySelectorAll('th, td'); // 获取每个行中的单元格(包括 <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;
// 解析 <w:num> 和 <w:abstractNum>
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 || "<p>未检测到表格内容。</p>";
const container = document.createElement("div");
container.innerHTML = tableHtml;
callback(this.updateTableStyles(container), []); // 返回处理后的 HTML
} catch (error) {
console.error("文件解析失败:", error);
callback("<p>文件解析失败,请检查文件格式。</p>");
}
},
// 转换段落为 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 = `<sup>${text}</sup>`;
} else if (isSubscript) {
text = `<sub>${text}</sub>`;
}
// 拼接到段落 HTML 中
paragraphHtml += text;
});
// 将运行的文本拼接成完整的段落
html += `<p>${paragraphHtml}</p>`;
});
return html;
}
,
convertTablesToHtml(tables, numberingMap) {
const namespace = "http://schemas.openxmlformats.org/wordprocessingml/2006/main";
let html = "";
// 遍历所有表格
for (const table of tables) {
html += "<table border='1' style='border-collapse: collapse; width: 100%;'>";
const rows = table.getElementsByTagNameNS(namespace, "tr");
for (const row of rows) {
html += "<tr>";
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 += "<br>";
}
cellContent += `<p>${paragraphContent}</p>`;
}
// 检查合并单元格属性
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 += `<td colspan="${colspan}" rowspan="${rowspan}">${cellContent}</td>`;
}
html += "</tr>";
}
html += "</table><br/>";
}
return html;
},
// 更新表格样式
// 更新表格样式(如果需要额外样式,添加在这里)
getWordTablesHtml(tables, callback) {
let combinedHtml = '';
// 遍历每一个表格
tables.forEach((table) => {
let tableHtml = `
<table border="1" style="border-collapse: collapse; width: 100%; text-align: center; margin-bottom: 16px;">
`;
// 遍历行
table.forEach((row) => {
tableHtml += `<tr>`;
// 遍历单元格
row.forEach((cell) => {
tableHtml += `
<td
colspan="${cell.colspan || 1}"
rowspan="${cell.rowspan || 1}"
style=""
>
<span > ${cell.text}</span>
</td>
`;
});
tableHtml += `</tr>`;
});
tableHtml += `</table>`;
combinedHtml += tableHtml;
});
// 创建容器元素
const container = document.createElement("div");
container.innerHTML = combinedHtml;
// 调用回调函数并返回最终结果
callback(this.updateTableStyles(container));
},
getWordTablesThumbnails(tables, callback, imgWidth, imgHeight, scale) {
let combinedHtml = `
<div style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: start;">
`;
// 遍历每个表格,生成缩略图
tables.forEach((table, index) => {
var tableStr = `<table
border="1"
style="
border-collapse: collapse;
width: 100%;
text-align: center;
table-layout: auto;"
>
`
table.forEach((row) => {
tableStr += `<tr>`;
// 遍历单元格
row.forEach((cell) => {
tableStr += `
<td
colspan="${cell.colspan || 1}"
rowspan="${cell.rowspan || 1}"
style=""
>
<span > ${cell.text}</span>
</td>
`;
});
tableStr += `</tr>`;
});
tableStr += `</table>`;
combinedHtml += `<div class="thumbnailTableBox"
style="border-radius: 4px; box-sizing: border-box; border: 1px solid #ccccccb5; width: ${imgWidth || '48%'}; height: ${imgHeight || '140px'}; overflow: hidden; position: relative; cursor: pointer;"
onclick="document.getElementById('table-modal-'+${index}).style.display = 'flex';" >
<div
id="thumbnail-container-table-${index}"
style="position: relative; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden;">
${tableStr}
</div>
</div>`
this.createImageModal(index, `<div class="wordTableHtml" style="background:#FFF;">${tableStr}</div>`, 'table', '');
})
combinedHtml += `</div>`;
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 = `
<div style="display: flex; flex-wrap: wrap; gap: 10px; justify-content: start;">
`;
// 遍历每个图片,生成缩略图
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 = `<img src="${mediaUrl + img.image}" alt="Image ${index}" style="width: 100%; height: auto;">`;
} else if (extension === 'tif') {
thumbnailContent = `<canvas id="tiff-canvas-${index} ${img.article_image_id ? `tiff-canvas-${img.article_image_id}` : ''}" style="width: 100%; height: auto;"></canvas>`;
} else {
thumbnailContent = `<a href="${mediaUrl + img.image}" style="color: #75abf1;width:100%; height: 100%;">
<div
style="width:100%; height: 100%;text-align:center;font-size:40px;display: flex;letter-spacing:1px;
justify-content: center;align-items:center;">
<span style="margin-right:10px;">${extension.toUpperCase()}</span> <i class="el-icon-download download" style="font-weight: bold;
color: #75abf1;
"></i>
</div> </a>`;
}
const modalContent = (['jpg', 'jpeg', 'png'].includes(extension))
? `<img src="${mediaUrl + img.image}" alt="Image ${index}" style="width: 100%; height: auto;">`
: `<canvas id="tiff-canvas-modal-${index}" style="width: 100%; height: auto;"></canvas>`;
// 创建缩略图容器
combinedHtml += `
<div class="thumbnailBox"
style="border-radius: 4px; box-sizing: border-box; border: 1px solid #ccccccb5; width: ${imgWidth || '48%'}; height: ${imgHeight || '140px'}; overflow: hidden; position: relative; cursor: pointer;"
onclick="document.getElementById('img-modal-'+${index}).style.display = 'flex';" >
<div
id="thumbnail-container-${index}"
style="position: relative; width: 100%; height: 100%; display: flex; justify-content: center; align-items: center; overflow: hidden;">
${thumbnailContent}
<div id="el-input-container-${index}"></div>
</div>
</div>
`;
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 += `</div>`;
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));
// 替换 <a> 标签为其内部文本
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'); // 获取第一个 <tr> 中的所有 <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 = '<table>';
// const rows = table.getElementsByTagName('w:tr'); // 表格行
// for (let row of rows) {
// html += '<tr>';
// 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 += `<td colspan="${colspan}" rowspan="${rowspan}">${cellContent}</td>`;
// }
// html += '</tr>';
// }
// html += '</table>';
// 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) {
// 如果是 <sup> 或 <sub> 标签,返回 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(/&nbsp;/g, ' ');
content = content.replace(/\s*style="[^"]*"/g, '');
var div = document.createElement('div');
div.innerHTML = content; // 将 HTML 字符串加载到 div 中
// 替换所有 <strong> 为 <b>
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]);
}
// 替换所有 <em> 为 <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;
// 检查是否包含 <img> 标签
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 () {
// 在选中的文本周围包裹 <blue> 标签
var selectedText = ed.selection.getContent();
console.log('selectedText at line 529:', selectedText);
if (selectedText) {
var wrappedText = `<blue>${selectedText}</blue>`;
ed.selection.setContent(wrappedText);
} else {
this.$message.error('请选择要添加蓝色的文本');
}
}
});
ed.ui.registry.addButton('LateX', {
text: 'LateX', // 按钮文本
className: 'custom-button-blue', // 添加自定义类
// shortcut: "Ctrl+J",
onAction: function () {
// 在选中的文本周围包裹 <blue> 标签
// var selectedText = ed.selection.getContent();
// console.log('selectedText at line 529:', selectedText);
// if (selectedText) {
// var wrappedText = `<blue>${selectedText}</blue>`;
// ed.selection.setContent(wrappedText);
// } else {
// this.$message.error('请选择要添加蓝色的文本');
// }
}
});
ed.ui.registry.addButton('myuppercase', {
text: 'A', // 按钮文本
onAction: function () {
// 在选中的文本周围包裹 <blue> 标签
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 () {
// 在选中的文本周围包裹 <blue> 标签
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;
// 向上查找最近的 <blue> 标签
while (startNode) {
if (startNode.nodeName && startNode.nodeName.toLowerCase() === 'blue') {
closestBlue = startNode;
break;
}
startNode = startNode.parentNode;
}
if (closestBlue) {
// 如果找到最近的 <blue> 标签,移除它的外层标签,但保留内容
const parent = closestBlue.parentNode;
while (closestBlue.firstChild) {
parent.insertBefore(closestBlue.firstChild, closestBlue);
}
parent.removeChild(closestBlue);
} else {
// 未找到 <blue> 标签,仅移除选中的 <blue> 标签内容
const selectedContent = ed.selection.getContent({ format: 'html' });
// 使用正则表达式移除选区中的 <blue> 标签
const cleanedContent = selectedContent
.replace(/<blue[^>]*>/g, '') // 删除起始标签 <blue>
.replace(/<\/blue>/g, ''); // 删除结束标签 </blue>
// 更新选中的内容
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 };