This commit is contained in:
2025-05-29 16:57:07 +08:00
parent 845785686d
commit 5f7f1d992b
18 changed files with 2144 additions and 3696 deletions

View File

@@ -54,6 +54,17 @@ function emuToPixels(emu) {
// 四舍五入并保留两位小数
return (Math.round(emuToPixels * 100) / 100).toFixed(2);
}
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 {
isImageValid(base64) {
@@ -79,113 +90,118 @@ export default {
handleFileUpload(event, callback) {
const file = event.target.files[0]; // 获取用户上传的文件
if (file && file.type === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
const reader = new FileReader();
reader.onload = (e) => {
// 将文件读取为 ArrayBuffer
const arrayBuffer = e.target.result;
// 使用 JSZip 解析 .docx 文件内容
const zip = new JSZip();
zip.loadAsync(arrayBuffer).then((zip) => {
// 获取 Word 文档的 XML 内容
const documentXml = zip.files['word/document.xml'];
const images = {}; // 用来保存图片的 src 和对应的宽高
if (documentXml) {
documentXml.async("string").then((xmlString) => {
// 使用正则或 XML 解析器提取所有图片的宽高
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, "text/xml");
// 查找图片的宽高(通常在 <wp:extent> 标签内)
const imageElements = xmlDoc.getElementsByTagName('wp:extent');
Array.from(imageElements).forEach((imgElement, index) => {
console.log('imgElement at line 78:', imgElement)
// 提取 width 和 height 属性
const widthEmu = imgElement.getAttribute('cx'); // 宽度
const heightEmu = imgElement.getAttribute('cy'); // 高度
if (widthEmu && heightEmu) {
// 转换为像素
const width = emuToPixels(widthEmu);
const height = emuToPixels(heightEmu);
images[index] = { width, height }; // 保存图片的宽高
}
// 这里你可以将宽高传递给回调函数
});
});
}
mammoth.convertToHtml({
arrayBuffer: e.target.result
})
.then((result) => {
console.log('images at line 115:', images)
// 使用正则提取所有表格内容
const tableContent = result.value.match(/<table[\s\S]*?<\/table>/g);
if (tableContent) {
console.log('tableContent at line 20:', tableContent);
// 筛选出包含 <td> 的表格
const validTables = tableContent.filter(table => /<td[\s\S]*?>/g.test(table));
console.log('validTables at line 71:', validTables);
// 提取表格内的图片
validTables.forEach((table, index) => {
console.log('table at line 75:', table)
const imgTags = table.match(/<img[\s\S]*?src="([^"]*)"/g);
console.log('imgTags at line 128:', imgTags)
if (imgTags) {
// 遍历所有图片标签
imgTags.forEach((imgTag, imgIndex) => {
console.log('imgTag at line 128:', imgTag);
const srcMatch = imgTag.match(/src="([^"]*)"/);
if (srcMatch) {
const imageInfo = images[imgIndex]; // 从 images 中查找对应的宽高
if (imageInfo) {
// 构建新的 <img> 标签,保留原来的其他属性
const newImgTag = imgTag.replace(
/<img/,
`<img width="${imageInfo.width}" height="${imageInfo.height}"`
);
table = table.replace(imgTag, newImgTag); // 替换旧标签
}
}
});
}
validTables[index] = table; // 更新该表格内容
});
if (validTables.length > 0) {
console.log('validTables.length at line 147:', validTables)
callback(validTables);
} else {
callback([]);
}
} else {
console.log("没有找到表格内容。");
}
})
.catch((err) => {
console.error('Error parsing Word file:', err);
});;
}).catch((err) => {
console.error("Error loading zip file:", err);
});
};
reader.readAsArrayBuffer(file); // 将文件读取为 ArrayBuffer
} else {
const file = event.target.files[0];
if (!file || file.type !== 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') {
alert('请上传一个有效的 Word 文件');
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'); // e.g., 'media/image1.jpeg'
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);
const mediaFile = rels[embedId];
if (mediaFile) {
imageInfoMap[mediaFile] = { width, height };
}
}
}
});
mammoth.convertToHtml({ arrayBuffer },
{
convertImage: mammoth.images.inline(async function (image) {
const contentType = image.contentType.toLowerCase();
// 只允许这三种格式
const allowedTypes = ['image/jpeg', 'image/jpg', 'image/png'];
if (!allowedTypes.includes(contentType)) {
// 跳过不支持的格式(如 image/tiff、image/x-emf 等)
return { src: '' }; // 会从 HTML 中删除这张图片
}
// 读取为 base64 并构造 src
const imageBuffer = await image.read("base64");
const base64Src = `data:${contentType};base64,${imageBuffer}`;
return {
src: base64Src
};
})
})
.then((result) => {
let html = result.value;
// html = html.replace(/<img[^>]+src="data:image\/x-emf[^"]*"[^>]*>/gi, '');
// 替换图片标签中的宽高
const imgTags = html.match(/<img[^>]*src="data:image\/[^"]+image(\d+)\.(png|jpg|jpeg)[^"]*"[^>]*>/gi);
if (imgTags) {
imgTags.forEach((imgTag) => {
const match = imgTag.match(/image(\d+)\.(png|jpg|jpeg)/);
if (match) {
const filename = `media/image${match[1]}.${match[2]}`;
const info = imageInfoMap[filename];
if (info) {
const newImgTag = imgTag.replace(
/<img/,
`<img width="${info.width}" height="${info.height}"`
);
html = html.replace(imgTag, newImgTag);
}
}
});
}
// 提取合法表格
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);
},
extractLatexFromMathJax() {
// 获取所有 MathJax 渲染的公式容器
const mathContainers = document.querySelectorAll('mjx-container');
@@ -193,17 +209,15 @@ export default {
mathContainers.forEach(container => {
// 查找每个渲染公式对应的 MathML 部分
const mathElement = container.querySelector('mjx-math');
console.log('mathElement at line 28:', mathElement)
if (mathElement) {
// 获取 MathJax 渲染的公式对象
const jax = window.MathJax.getJaxFor(mathElement);
console.log('jax at line 32:', jax)
if (jax) {
// 使用 MathJax API 获取公式的 LaTeX 代码
const latex = jax.getLiteral(); // 获取 LaTeX 表达式
console.log('提取到的 LaTeX 公式:', latex); // 输出 LaTeX 代码
} else {
console.warn('MathJax 对象未找到');
}
@@ -232,28 +246,23 @@ export default {
,
// **解析 MathJax 公式,获取 LaTeX**
async extractMathJaxLatex(cell, callback) {
console.log('cell at line 67:', cell)
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
console.log('cell content at the start:', updatedContent);
// 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');
console.log('LaTeX content from data-latex:', latexContent);
// Replace the <wmath> tag with its LaTeX content wrapped in $$...$$
updatedContent = updatedContent.replace(element.outerHTML, `<wmath data-latex="${latexContent}">${latexContent}</wmath>`);
});
console.log('updatedContent after processing wmath tags:', updatedContent);
// Step 2: Now extract content without the outer <span> tags
updatedContent = this.extractContentWithoutOuterSpan(updatedContent);
console.log('updatedContent after extractContentWithoutOuterSpan:', updatedContent);
// Step 3: Call the callback function with the final updated content
// callback(updatedContent);
@@ -277,7 +286,7 @@ export default {
},
//去掉最外层自定义的span标签
extractContentWithoutOuterSpan(cell) {
console.log('cell at line 90:', cell)
var str = ''
if (!cell) {
return ''
@@ -285,7 +294,6 @@ export default {
// 获取单元格的 HTML 内容
let htmlContent = cell.trim();
console.log('htmlContent at line 94:', htmlContent)
str = this.transformHtmlString(htmlContent, 'table')
@@ -326,8 +334,7 @@ export default {
console.log('str at line 141:', str)
// 如果没有 <span> 标签,直接返回原始 HTML 内容
return str;
},
@@ -413,7 +420,7 @@ export default {
allTables.push(tableArray); // 添加当前表格到所有表格数组
}
console.log("解析后的表格数组:", allTables);
callback(allTables); // 返回处理后的数组
} catch (error) {
console.error("解析粘贴内容失败:", error);
@@ -426,7 +433,6 @@ export default {
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) {
@@ -438,8 +444,6 @@ export default {
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) {
@@ -546,7 +550,7 @@ export default {
}
}
console.log('rowspan at line 265:', rowspan)
const currentLevelNumbers = {};
for (const paragraph of paragraphs) {
let listPrefix = "";
@@ -577,7 +581,7 @@ export default {
const embedId = blip.getAttribute("r:embed");
if (embedId) {
textContent += `<img data-embed="${embedId}"/>`;
console.log("✅ 图片 embedId:", embedId);
}
}
}
@@ -585,7 +589,7 @@ export default {
const texts = run.getElementsByTagName("w:t");
for (const text of texts) {
textContent += text.textContent;
@@ -632,7 +636,6 @@ export default {
});
}
console.log("After replacement:", formattedText);
paragraphText += formattedText;
}
@@ -643,7 +646,6 @@ export default {
}
cellText += paragraphText;
console.log('cellText at line 366:', cellText)
}
rowArray.push({
@@ -671,7 +673,6 @@ export default {
allTables.push(tableArray);
}
console.log("解析后的二维数组:", allTables);
callback(allTables);
} catch (error) {
@@ -951,7 +952,7 @@ export default {
let parsedData = Array.from(paragraphs).map(p => {
let text = p.innerHTML.trim(); // 获取内容,去除两端空格
text = replaceNegativeSign(text);
console.log('text at line 756:', text)
text = this.transformHtmlString(text)
// 3⃣ **正确移除 <o:p>Word 复制的无效标签)**
text = text.replace(/<\/?o:p[^>]*>/g, "");
@@ -977,7 +978,7 @@ export default {
return text.trim() === "" ? "" : text;
});
console.log(parsedData); // 输出数组,方便调试
return parsedData;
}
@@ -987,7 +988,6 @@ export default {
async parseTableToArray(tableString, callback) {
console.log('tableString at line 845:', tableString)
const parser = new DOMParser();
const doc = parser.parseFromString(tableString, 'text/html');
const rows = doc.querySelectorAll('table tr'); // 获取所有的行(<tr>
@@ -1008,7 +1008,7 @@ export default {
);
})
);
console.log('result at line 78611:', result)
callback(result)
// 返回处理后的数组
@@ -1095,7 +1095,6 @@ export default {
// 获取编号定义
const numberingMap = this.parseNumbering(numberingDoc);
console.log('numberingMap at line 232:', numberingMap)
// 提取表格
// 获取所有的段落
const paragraphs = xmlDoc.getElementsByTagName("w:p");
@@ -1115,9 +1114,7 @@ export default {
}
// 将前一段的内容转化为 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>";
@@ -1472,8 +1469,6 @@ export default {
// 保持原比例
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);
@@ -1866,7 +1861,6 @@ export default {
// 如果找到的 div 节点存在
if (outerDiv) {
const dataId = outerDiv.getAttribute('main-id');
console.log('dataId at line 1258:', dataId)
vueInstance.$emit('onAddRow', dataId);
}
@@ -1889,7 +1883,6 @@ export default {
if (outerDiv) {
const dataId = outerDiv.getAttribute('main-id');
var content;
console.log('outerDiv at line 663:', outerDiv.innerHTML);
content = outerDiv.innerHTML.replace(/<(?!\/?(img|b|i|sub|sup|span|strong|em |blue)\b)[^>]+>/g, '');
content = content.replace(/<([a-zA-Z]+)>\s*<\/\1>/g, '');
content = content.replace(/&nbsp;/g, ' ');
@@ -1932,7 +1925,6 @@ export default {
// 如果找到的 div 节点存在
if (outerDiv) {
const dataId = outerDiv.getAttribute('main-id');
console.log('dataId at line 1258:', dataId)
vueInstance.$emit('onEditTitle', {
mainId: dataId,
value: 1
@@ -1954,7 +1946,6 @@ export default {
// 如果找到的 div 节点存在
if (outerDiv) {
const dataId = outerDiv.getAttribute('main-id');
console.log('dataId at line 1258:', dataId)
vueInstance.$emit('onEditTitle', {
mainId: dataId,
value: 2
@@ -1976,7 +1967,6 @@ export default {
// 如果找到的 div 节点存在
if (outerDiv) {
const dataId = outerDiv.getAttribute('main-id');
console.log('dataId at line 1258:', dataId)
vueInstance.$emit('onEditTitle', {
mainId: dataId,
value: 3
@@ -1998,7 +1988,6 @@ export default {
// 如果找到的 div 节点存在
if (outerDiv) {
const dataId = outerDiv.getAttribute('main-id');
console.log('dataId at line 1258:', dataId)
vueInstance.$emit('onEdit', dataId);
}
@@ -2023,7 +2012,6 @@ export default {
const dataId = outerDiv.getAttribute('main-id');
const type = outerDiv.getAttribute('type');
console.log('type:', type);
// 获取选中的内容HTML格式
let selectedContent = edSelection.getContent({ format: 'html' });
@@ -2142,7 +2130,6 @@ export default {
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);
@@ -2161,7 +2148,6 @@ export default {
// 1. 获取当前光标位置
const latexEditorBookmark = ed.selection.getBookmark(2); // 获取光标位置
const editorId = ed.id; // 保存当前编辑器 ID
console.log('activeEditorId:', editorId);
// 2. 生成一个随机的 ID用于 wmath 标签
const uid = 'wmath-' + Math.random().toString(36).substr(2, 9);