Files
tougao_web/src/common/js/commonJS.js

2248 lines
95 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 katex from 'katex';
import JSZip from 'jszip';
import mammoth from "mammoth";
import api from '../../api/index.js';
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(/[-]/g, "");
// };
const replaceNegativeSign = function (text) {
// 1. 替换范围中的负号(`[13-22]` 或 `[1322]`
text = text.replace(/(\[\d+)[-](\d+\])/g, '$1$2');
// 2. 替换单独负号(`-22` 或 `22`
text = text.replace(/[-](?=\d)/g, "");
return text;
};
// 首字母大写的方法
// const capitalizeFirstLetter = function (text) {
// return text.replace(/^\s*([a-zA-Z])/, function (match, firstLetter) {
// return firstLetter.toUpperCase();
// });
// };
const capitalizeFirstLetter = function (text) {
// const words = text.split(' '); // 分割字符串为单词数组
// if (words.length > 0 && words[0].charAt(0).match(/[a-zA-Z]/)) {
// // 如果第一个字符是字母,则将第一个单词的首字母大写
// words[0] = words[0].charAt(0).toUpperCase() + words[0].slice(1);
// }
return text; // 将处理后的单词数组连接成字符串
};
//cm
// function emuToPixels(emu) {
// const emuToCm = emu / 914400 * 2.54;
// // 四舍五入并保留两位小数
// return (Math.round(emuToCm * 100) / 100).toFixed(2);
// }
//px
function emuToPixels(emu) {
// 将 EMU 转换为厘米,并进一步转换为像素
const emuToPixels = emu * 96 / 914400;
// return parseFloat((emu * 96 / 914400).toFixed(2)); // ✅
// 四舍五入并保留两位小数
return (Math.round(emuToPixels * 100) / 100).toFixed(0);
}
function findExtentElement(blipElement) {
let current = blipElement.parentElement;
while (current) {
const extents = current.getElementsByTagName('wp:extent');
if (extents.length > 0) {
return extents[0]; // 找到包含 wp:extent 的节点
}
current = current.parentElement;
}
return null; // 没有找到
}
export default {
getJournalTypeName(value) {
var list = JSON.parse(localStorage.getItem('journalTypeDataAll'));
if (list && list.length > 0) {
const type = list.find(item => item.value === value);
return type ? type.name : 'OTHERS';
} else {
return ''
}
},
journalTypeList(type) {
var journal_type = JSON.parse(localStorage.getItem('journalTypeData'));
return journal_type;
},
opMedicalList() {
var opMedical = JSON.parse(localStorage.getItem('opMedicalListData'))
return opMedical;
},
isImageValid(base64) {
return new Promise((resolve, reject) => {
// 创建 Image 对象
const img = new Image();
// 设置图片的 src 属性
img.src = base64;
// 图片加载成功
img.onload = function () {
resolve(true); // 图片有效
};
// 图片加载失败
img.onerror = function () {
resolve(false); // 图片无效
};
});
},
extractLatexFromMathJax() {
// 获取所有 MathJax 渲染的公式容器
const mathContainers = document.querySelectorAll('mjx-container');
mathContainers.forEach(container => {
// 查找每个渲染公式对应的 MathML 部分
const mathElement = container.querySelector('mjx-math');
if (mathElement) {
// 获取 MathJax 渲染的公式对象
const jax = window.MathJax.getJaxFor(mathElement);
if (jax) {
// 使用 MathJax API 获取公式的 LaTeX 代码
const latex = jax.getLiteral(); // 获取 LaTeX 表达式
} else {
console.warn('MathJax 对象未找到');
}
}
});
},
replaceWMathContent(inputHtml, callback) {
// 使用正则表达式查找所有 <wmath> 标签,并提取 data-latex 的内容
var str = inputHtml.replace(/<wmath data-latex="([^"]+)">[^<]*<\/wmath>/g, function (match, latexContent) {
// 返回 <wmath> 标签,内容替换为 data-latex 的值
return `<wmath data-latex="${latexContent}">${latexContent}</wmath>`;
});
// 调用回调函数并传递处理后的结果
callback(str);
// 输出结果到控制台
// console.log('Processed HTML:', str);
}
,
// **解析 MathJax 公式,获取 LaTeX**
async extractMathJaxLatex(cell, callback) {
return new Promise((resolve, reject) => {
// Step 1: First, process the math content and extract LaTeX from <wmath> tags
let updatedContent = cell.innerHTML; // Start with the cell's inner HTML
// Find all <wmath> elements
const wmathElements = cell.querySelectorAll('wmath');
wmathElements.forEach((element) => {
// Get the LaTeX content from the data-latex attribute
const latexContent = element.getAttribute('data-latex');
// Replace the <wmath> tag with its LaTeX content wrapped in $$...$$
updatedContent = updatedContent.replace(element.outerHTML, `<wmath data-latex="${latexContent}">${latexContent}</wmath>`);
});
// Step 2: Now extract content without the outer <span> tags
updatedContent = this.extractContentWithoutOuterSpan(updatedContent);
// Step 3: Call the callback function with the final updated content
// callback(updatedContent);
// Resolve the promise with the final content
resolve(updatedContent);
});
}
,
renderLatex(latexString) {
return katex.renderToString(latexString, {
throwOnError: false
});
},
decodeHtml(html) {
var txt = document.createElement('textarea');
txt.innerHTML = html;
return txt.value;
},
//去掉最外层自定义的span标签
extractContentWithoutOuterSpan(cell) {
var str = ''
if (!cell) {
return ''
}
// 获取单元格的 HTML 内容
let htmlContent = cell.trim();
str = this.transformHtmlString(htmlContent, 'table')
// 创建一个临时的 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; // 如果不符合条件,则保持原样
});
}
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); // 添加当前表格到所有表格数组
}
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);
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");
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++;
nextRowIdx++;
} else {
break;
}
} else if (vMergeVal === "restart") {
break;
} else {
break;
}
} else {
break;
}
}
} else {
continue;
}
}
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 drawings = run.getElementsByTagName("w:drawing");
for (let d = 0; d < drawings.length; d++) {
const drawing = drawings[d];
// 使用命名空间提取 a:blip
const blips = drawing.getElementsByTagNameNS("http://schemas.openxmlformats.org/drawingml/2006/main", "blip");
for (let b = 0; b < blips.length; b++) {
const blip = blips[b];
const embedId = blip.getAttribute("r:embed");
if (embedId) {
textContent += `<img data-embed="${embedId}"/>`;
}
}
}
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;
});
}
paragraphText += formattedText;
}
const breaks = paragraph.getElementsByTagName("w:br");
for (const br of breaks) {
paragraphText += "<br/>";
}
cellText += paragraphText;
}
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);
}
callback(allTables);
} catch (error) {
console.error("解析 Word 文件失败:", error);
callback([]);
}
},
handleFileUpload(event, callback) {
const file = event.target.files[0];
if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
alert('Please upload a valid Word file !');
return;
}
const reader = new FileReader();
reader.onload = (e) => {
const arrayBuffer = e.target.result;
const zip = new JSZip();
zip.loadAsync(arrayBuffer).then(async (zip) => {
const relsXml = await zip.files['word/_rels/document.xml.rels'].async('string');
const docXml = await zip.files['word/document.xml'].async('string');
const parser = new DOMParser();
const relDoc = parser.parseFromString(relsXml, "text/xml");
const docDom = parser.parseFromString(docXml, "text/xml");
const rels = {};
Array.from(relDoc.getElementsByTagName('Relationship')).forEach((rel) => {
const id = rel.getAttribute('Id');
const target = rel.getAttribute('Target');
rels[id] = target;
});
const imageInfoMap = {};
const blips = docDom.getElementsByTagName('a:blip');
Array.from(blips).forEach((blip) => {
const embedId = blip.getAttribute('r:embed');
const extent = findExtentElement(blip);
if (embedId && extent) {
const cx = extent.getAttribute('cx');
const cy = extent.getAttribute('cy');
if (cx && cy) {
const width = emuToPixels(cx);
const height = emuToPixels(cy);
imageInfoMap[embedId] = { width, height };
}
}
});
mammoth.convertToHtml({ arrayBuffer }, {
convertImage: mammoth.images.inline(async function (image) {
console.log('image at line 163:', image)
const contentType = image.contentType.toLowerCase();
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
if (!allowedTypes.includes(contentType)) {
return { src: '' };
}
const embedId = image.relationshipId || image.refId || '';
const imageBuffer = await image.read("base64");
const base64Src = `data:${contentType};base64,${imageBuffer}`;
let width = '', height = '';
if (embedId && imageInfoMap[embedId]) {
width = imageInfoMap[embedId].width;
height = imageInfoMap[embedId].height;
}
return {
src: base64Src,
alt: '',
width,
height,
refId: embedId,
'content-type': contentType
};
})
}).then((result) => {
let html = result.value;
// 提取合法表格
const tableContent = html.match(/<table[\s\S]*?<\/table>/g);
const validTables = tableContent
? tableContent.filter(table => /<td[\s\S]*?>/.test(table))
: [];
callback(validTables);
}).catch(err => {
console.error('mammoth 转换失败:', err);
});
}).catch(err => {
console.error("Zip 读取失败:", err);
});
};
reader.readAsArrayBuffer(file);
},
// 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, type, options = {}) {
const { keepBr = false } = options;
if (!keepBr) {
inputHtml = inputHtml.replace(/<br\s*\/?>/g, '');
}
// inputHtml = inputHtml.replace(/(<[^>]+) style="[^"]*"/g, '$1'); // 移除style属性
// inputHtml = inputHtml.replace(/(<[^>]+) class="[^"]*"/g, '$1'); // 移除class属性
// inputHtml = inputHtml.replace(/<([a-zA-Z0-9]+)[^>]*>/g, '<$1>'); // 删除标签上的所有属性
inputHtml = inputHtml.replace(/<([a-zA-Z0-9]+)([^>]*)>/g, function (match, tag, attributes) {
// 使用正则表达式删除属性(保留 data-latex
let updatedAttributes = attributes.replace(/\s([a-zA-Z0-9-]+)(="[^"]*")?/g, function (attrMatch, attrName) {
if (attrName === "data-latex" || attrName === "data-id") {
return attrMatch;
}
if (type == 'table' && tag == 'img' && (attrName === "src" || attrName === "width" || attrName === "height")) {
return attrMatch;
}
return '';
});
return `<${tag}${updatedAttributes}>`;
});
// 2. 删除所有不需要的标签 (除 `strong`, `em`, `sub`, `sup`, `b`, `i` 外的所有标签)
if (type == 'table') {
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|img|myfigure|mytable))[^>]+>/g, ''); // 删除不需要的标签
} else {
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|myfigure|mytable))[^>]+>/g, ''); // 删除不需要的标签
}
// 去掉样式
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. 合并相同标签
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(); // 获取内容,去除两端空格
text = replaceNegativeSign(text);
text = this.transformHtmlString(text)
// 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;
});
return parsedData;
}
,
async parseTableToArray(tableString, callback) {
const parser = new DOMParser();
const doc = parser.parseFromString(tableString, 'text/html');
const rows = doc.querySelectorAll('table tr'); // 获取所有的行(<tr>
// 使用 Promise 来处理异步的 MathJax 解析
const result = await Promise.all(
Array.from(rows).map(async (row) => {
const cells = row.querySelectorAll('th, td'); // 获取每个行中的单元格(包括 <th> 和 <td>
return await Promise.all(
Array.from(cells).map(async (cell) => {
const text = await this.extractMathJaxLatex(cell);
return {
text,
colspan: cell.getAttribute('colspan') ? parseInt(cell.getAttribute('colspan')) : 1, // 提取 colspan默认值为 1
rowspan: cell.getAttribute('rowspan') ? parseInt(cell.getAttribute('rowspan')) : 1 // 提取 rowspan默认值为 1
};
})
);
})
);
callback(result)
// 返回处理后的数组
},
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);
// 提取表格
// 获取所有的段落
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);
const tableHtml = this.convertTablesToHtml(tables, numberingMap);
// 更新 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>`;
if (row && row.length > 0) {
row.forEach((cell) => {
tableHtml += `
<td
colspan="${cell.colspan || 1}"
rowspan="${cell.rowspan || 1}"
style=""
>
<span > ${cell.text}</span>
</td>
`;
});
} else {
tableHtml += ``
}
// 遍历单元格
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);
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); // 替换负号和减号为 EN DASH
}
// 检查当前 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());
node.nodeValue = node.nodeValue;
} 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');
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;
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');
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');
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');
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');
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');
// 获取选中的内容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',
onAction: function () {
// 必须获取带 HTML 的内容,否则里面的 em/i 标签在拼接前就丢了
var selectedText = ed.selection.getContent({ format: 'html' });
if (selectedText) {
// 这就是你想要的:直接外层套一个 blue
var wrappedText = `<blue>${selectedText}</blue>`;
// 使用 setContent 强行回写
ed.selection.setContent(wrappedText);
}
}
});
let latexEditorBookmark = null; // 用于记录插入点
let activeEditorId = null; // 当前激活的编辑器 ID
// 在编辑器工具栏中添加 "LateX" 按钮
ed.ui.registry.addButton('LateX', {
text: 'LateX', // 按钮文本
onAction: function () {
// 1. 获取当前光标位置
const latexEditorBookmark = ed.selection.getBookmark(2); // 获取光标位置
const editorId = ed.id; // 保存当前编辑器 ID
// 2. 生成一个随机的 ID用于 wmath 标签
const uid = 'wmath-' + Math.random().toString(36).substr(2, 9);
// 3. 创建一个 <wmath> 标签并插入到光标处
const wmathHtml = `<wmath contenteditable="false" data-id="${uid}" data-latex=""></wmath>`;
ed.insertContent(wmathHtml); // 在光标位置插入 wmath 标签
// 4. 打开公式编辑器窗口,并传递光标位置、编辑器 ID 和 wmathId
const url = `/LateX?editorId=${editorId}&wmathId=${uid}`;
// vueInstance.openLatexEditor({
// editorId:editorId,
// wmathId:uid,
// });
window.open(url, '_blank', 'width=1000,height=800,scrollbars=no,resizable=no');
}
});
// 全部大写按钮按钮文本A
ed.ui.registry.addButton('myuppercase', {
text: 'A', // 按钮文本(大写标识)
onAction: function () {
// 获取选中的文本(保留 HTML 格式,确保空格等内容不丢失)
var selectedText = ed.selection.getContent({ format: 'html' });
// 校验:非空且仅含字母、数字、空格(可根据需求调整正则)
// if (selectedText.trim() && /^[\s\w]+$/.test(selectedText)) {
// 直接将选中的所有内容转为大写(无需正则,整体转换)
var allUppercaseText = selectedText.toUpperCase();
// 替换选中的文本
ed.selection.setContent(allUppercaseText);
// } else {
// vueInstance.$message.error(vueInstance.$t('commonTable.selectWord'));
// }
}
});
// 全部小写按钮按钮文本a
ed.ui.registry.addButton('myuppercasea', {
text: 'a', // 按钮文本(小写标识)
onAction: function () {
var selectedText = ed.selection.getContent({ format: 'html' });
// if (selectedText.trim() && /^[\s\w]+$/.test(selectedText)) {
// 直接将选中的所有内容转为小写(整体转换)
var allLowercaseText = selectedText.toLowerCase();
ed.selection.setContent(allLowercaseText);
// } else {
// vueInstance.$message.error(vueInstance.$t('commonTable.selectWord'));
// }
}
});
ed.ui.registry.addButton('Line', {
text: '', // 按钮文本
onAction: function () {
// 插入 `` 符号到当前光标位置
ed.insertContent('');
}
});
ed.ui.registry.addMenuButton('MoreSymbols', {
text: '···', // 按钮显示的三个点
tooltip: 'More Special Characters',
onAction: () => {}, // 菜单主按钮点击通常不执行操作,由子菜单执行
fetch: (callback) => {
const items = [
{
type: 'menuitem',
text: 'en-dash (短划线)',//短划线
onAction: () => ed.insertContent('')
},
{
type: 'menuitem',
text: 'minus sign (减号)',//减号
onAction: () => ed.insertContent('')
},
{
type: 'menuitem',
text: 'hyphen (连接符)',
onAction: () => ed.insertContent('-')
},
];
callback(items);
}
});
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 };