1994 lines
84 KiB
JavaScript
1994 lines
84 KiB
JavaScript
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() || " "; // 处理空值
|
||
|
||
// 处理样式
|
||
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(/ /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 };
|
||
|
||
|