tijiao
This commit is contained in:
@@ -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(/ /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);
|
||||
|
||||
Reference in New Issue
Block a user