diff --git a/src/api/index.js b/src/api/index.js index 487fb66..525753d 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -19,8 +19,8 @@ const service = axios.create({ // baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换 // baseURL: 'http://www.tougao.com/', //测试本地 记得切换 // baseURL: 'http://192.168.110.110/tougao/public/index.php/', - baseURL: '/api', //本地 - // baseURL: '/', //正式 + // baseURL: '/api', //本地 + baseURL: '/', //正式 }); diff --git a/src/common/js/commonJS.js b/src/common/js/commonJS.js index c1a47de..2f22c63 100644 --- a/src/common/js/commonJS.js +++ b/src/common/js/commonJS.js @@ -150,49 +150,49 @@ export default { replaceWMathContent(inputHtml, callback) { // 正则逻辑:匹配整个 wmath 标签,并捕获内部所有的属性字符串 var str = inputHtml.replace(/]+)>[^<]*<\/wmath>/g, function (match, allProps) { - + // 1. 从所有属性中提取 data-latex 的值 const latexMatch = allProps.match(/data-latex="([^"]+)"/); const latexContent = latexMatch ? latexMatch[1] : ''; - + // 2. 从所有属性中提取 data-wrap 的值 const wrapMatch = allProps.match(/data-wrap="([^"]+)"/); const wrapAttr = wrapMatch ? ` data-wrap="${wrapMatch[1]}"` : ''; - + // 3. 重新组装:只保留 data-latex 和 data-wrap // 注意:这里去掉了多余的 prefix/suffix,确保标签“干净” return `${latexContent}`; }); - + callback(str); - } , + }, // **解析 MathJax 公式,获取 LaTeX** async extractMathJaxLatex(cell, callback) { return new Promise((resolve, reject) => { // Step 1: 获取初始 HTML let updatedContent = cell.innerHTML; - + // 查找所有 元素 const wmathElements = cell.querySelectorAll('wmath'); - + wmathElements.forEach((element) => { // 1. 提取 latex 内容 const latexContent = element.getAttribute('data-latex') || ''; - + // 2. 提取 wrap 模式 (只提取,不包裹) const wrapMode = element.getAttribute('data-wrap') || 'block'; - + // 3. 重新组装标签:只保留属性,标签内部只放原始 latex 文本 // 这里不加 $ 或 $$,保持数据的原始性 const newWmathTag = `${latexContent}`; - + // 4. 执行替换 updatedContent = updatedContent.replace(element.outerHTML, newWmathTag); }); - + // Step 2: 提取去掉外层 span 的内容 updatedContent = this.extractContentWithoutOuterSpan(updatedContent); - + // Step 3: Resolve 结果 resolve(updatedContent); }); @@ -350,6 +350,21 @@ export default { console.error("❌ 找不到 word/document.xml,无法解析 Word 文件"); return; } + const relsFile = zip.file("word/_rels/document.xml.rels"); + let imageRelMap = {}; + if (relsFile) { + const relsXml = await relsFile.async("string"); + const relDoc = new DOMParser().parseFromString(relsXml, "application/xml"); + const rels = relDoc.getElementsByTagName("Relationship"); + for (let r = 0; r < rels.length; r++) { + const id = rels[r].getAttribute("Id"); + const target = rels[r].getAttribute("Target"); + if (target && target.includes("media/")) { + // 补全路径,通常 rels 里的路径是相对 word 文件夹的 + imageRelMap[id] = `word/${target}`; + } + } + } const documentXml = await documentFile.async("string"); const parser = new DOMParser(); const documentDoc = parser.parseFromString(documentXml, "application/xml"); @@ -458,14 +473,44 @@ export default { const drawings = run.getElementsByTagName("w:drawing"); for (let d = 0; d < drawings.length; d++) { const drawing = drawings[d]; - // 使用命名空间提取 a:blip + + // --- 1. 提取宽高 (EMUs 转 PX) --- + // Word 存储尺寸的地方通常在 wp:extent + const extent = drawing.getElementsByTagName("wp:extent")[0]; + let styleStr = ""; + if (extent) { + const cx = parseInt(extent.getAttribute("cx")); + const cy = parseInt(extent.getAttribute("cy")); + // 9525 是 EMU 到像素的换算比例 + const widthPx = Math.round(cx / 9525); + const heightPx = Math.round(cy / 9525); + styleStr = `width="${widthPx}" height="${heightPx}"`; + } + 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 += ``; - + + if (embedId && imageRelMap[embedId]) { + // const imagePath = imageRelMap[embedId]; + // const imgFile = zip.file(imagePath); + + // if (imgFile) { + // const fileSize = imgFile._data.uncompressedSize; + + // // 依然保留 1MB 限制,保护浏览器不卡死 + // if (fileSize <= 1048576) { + // const base64 = await imgFile.async("base64"); + // const ext = imagePath.split('.').pop().toLowerCase(); + // // + // // --- 2. 直接拼接带宽高的 img 标签 --- + // textContent += ``; + // } else { + // // console.warn(`跳过大图片: ${imagePath}, 大小: ${(fileSize / 1024 / 1024).toFixed(2)}MB`); + // textContent += ``; + // } + // } } } } @@ -2062,12 +2107,12 @@ export default { // 插入自定义表格到编辑器中 ed.setContent(''); const customCallback = ed.getParam('clear_custom_action'); - if (typeof customCallback === 'function') { - customCallback(ed, vueInstance); - } else { - // 3. 如果没有自定义逻辑,执行默认逻辑 - vueInstance.$emit('onClear'); - } + if (typeof customCallback === 'function') { + customCallback(ed, vueInstance); + } else { + // 3. 如果没有自定义逻辑,执行默认逻辑 + vueInstance.$emit('onClear'); + } } }); @@ -2079,11 +2124,11 @@ export default { onAction: function () { // 必须获取带 HTML 的内容,否则里面的 em/i 标签在拼接前就丢了 var selectedText = ed.selection.getContent({ format: 'html' }); - + if (selectedText) { // 这就是你想要的:直接外层套一个 blue var wrappedText = `${selectedText}`; - + // 使用 setContent 强行回写 ed.selection.setContent(wrappedText); } @@ -2166,29 +2211,29 @@ export default { ed.ui.registry.addMenuButton('MoreSymbols', { text: '···', // 按钮显示的三个点 tooltip: 'More Special Characters', - onAction: () => {}, // 菜单主按钮点击通常不执行操作,由子菜单执行 + 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); + 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', // 按钮文本 diff --git a/src/components/common/common.vue b/src/components/common/common.vue index 865442c..d0b6e1c 100644 --- a/src/components/common/common.vue +++ b/src/components/common/common.vue @@ -2,8 +2,8 @@ //记得切换 //正式 -// const mediaUrl = '/public/'; -// const baseUrl = '/'; +const mediaUrl = '/public/'; +const baseUrl = '/'; //正式环境 @@ -18,8 +18,8 @@ // const baseUrl = '/api'; ////新正式环境 -const mediaUrl = 'http://mytest.tmrjournals.com/public/'; -const baseUrl = '/api'; +// const mediaUrl = 'http://mytest.tmrjournals.com/public/'; +// const baseUrl = '/api'; //本地(正式环境 ) // const mediaUrl = 'https://submission.tmrjournals.com/public/'; diff --git a/src/components/page/articleAdd.vue b/src/components/page/articleAdd.vue index 496c84a..7160ddd 100644 --- a/src/components/page/articleAdd.vue +++ b/src/components/page/articleAdd.vue @@ -2804,7 +2804,7 @@ export default { that.$commonJS.extractWordTablesToArrays(File.raw, function (wordTables) { that.addWordTablesList(wordTables); loading.close(); - }); + },that.form.article_id); }; reader.readAsArrayBuffer(File.raw); } diff --git a/src/components/page/articleDetailEditor.vue b/src/components/page/articleDetailEditor.vue index 5751604..4640379 100644 --- a/src/components/page/articleDetailEditor.vue +++ b/src/components/page/articleDetailEditor.vue @@ -1298,7 +1298,7 @@ {{ `${i + 1}. ${v.topic} : ` }} -
+
+
+

+ {{ $t('aiReview.Explain') }} : {{ currentArticleData.ai_review[v.explanationValue] }} +

+
+
+
+
+
+ + + {{ `(${index + 1}) 领域 : ${item['领域']} ` }} + +
+
+ + 是否热点 : 【{{item['是否热点'] }}】 + + + +
+
+

+ {{ $t('aiReview.Explain') }} : + {{ item['解释说明'] }} +

+
+ + +
+
+
+
+
+
+ + +
+

{ diff --git a/src/components/page/articleListEditor_B1.vue b/src/components/page/articleListEditor_B1.vue index 2e058f9..643f1f1 100644 --- a/src/components/page/articleListEditor_B1.vue +++ b/src/components/page/articleListEditor_B1.vue @@ -2981,7 +2981,7 @@ export default { this.preactive = 1; // 获取文章信息 this.$api - .post('api/Production/getProductionMains', { + .post('api/Preaccept/getArticleMains', { p_article_id: this.p_article_id }) .then((res) => { diff --git a/src/components/page/components/Tinymce/index.vue b/src/components/page/components/Tinymce/index.vue index 2ccf643..c553177 100644 --- a/src/components/page/components/Tinymce/index.vue +++ b/src/components/page/components/Tinymce/index.vue @@ -347,44 +347,46 @@ export default { } }, // 按照你要求的 XMLHttpRequest 格式编写 - uploadSingleImage(base64Data, index) { - const _this = this; - //转为 Blob - const blob = _this.dataURLtoBlob(base64Data); - const xhr = new XMLHttpRequest(); - const formData = new FormData(); - // 按照你截图中的参数名,这里假设是 'file' - formData.append('file_name', blob, `word_img_${index}.png`); - formData.append('article_id', this.articleId); - xhr.withCredentials = false; - xhr.open('POST', _this.baseUrl + 'api/Articlemain/uploadTableImage'); - xhr.onload = function () { - if (xhr.status !== 200) { - console.error('HTTP Error: ' + xhr.status); - return; - } - try { - const json = JSON.parse(xhr.responseText); - if (json.status == 1) { - // 2. 拼接服务器返回的 URL - const finalUrl = _this.mediaUrl + 'articleTableImage/' + json.data; - // 3. 找到对应的加载中占位图并替换 - const doc = tinymce.activeEditor.getDoc(); - const placeholder = doc.querySelector(`img[data-idx="${index}"]`); - if (placeholder) { - placeholder.src = finalUrl; - placeholder.removeAttribute('data-idx'); // 任务完成,移除标记 - } - } else { - _this.removePlaceholder(index); - } - } catch (e) { - console.error('解析响应失败', e); - } - }; + // 直接接收已经转好的 blob +uploadSingleImage(blob, index) { + const _this = this; + const xhr = new XMLHttpRequest(); + const formData = new FormData(); + + // 直接把传进来的 blob 丢进表单 + formData.append('file_name', blob, `word_img_${index}.png`); + formData.append('article_id', this.articleId); - xhr.send(formData); - }, + xhr.withCredentials = false; + xhr.open('POST', _this.baseUrl + 'api/Articlemain/uploadTableImage'); + + xhr.onload = function () { + if (xhr.status !== 200) { + _this.removePlaceholder(index); // 失败清理 + return; + } + try { + const json = JSON.parse(xhr.responseText); + if (json.status == 1) { + const finalUrl = _this.mediaUrl + 'articleTableImage/' + json.data; + const doc = tinymce.activeEditor.getDoc(); + const placeholder = doc.querySelector(`img[data-idx="${index}"]`); + if (placeholder) { + placeholder.src = finalUrl; + placeholder.removeAttribute('data-idx'); + } + } else { + _this.removePlaceholder(index); + } + } catch (e) { + console.error('解析响应失败', e); + _this.removePlaceholder(index); + } + }; + + xhr.onerror = () => _this.removePlaceholder(index); + xhr.send(formData); +}, removePlaceholder(idx) { const doc = tinymce.activeEditor.getDoc(); @@ -591,13 +593,18 @@ myfigure *, _this.updateUploadProgressNotification(i, 'tooLarge'); return; // 跳过此图片 } - try { - const base64 = _this.hexToBase64Sync(img.hex, img.mimeType); - _this.updateUploadProgressNotification(i, 'uploading'); // 更新为上传中 - await _this.uploadSingleImage(base64, i); - _this.updateUploadProgressNotification(i, 'success'); // 上传成功 - } catch (err) { + + const imageBlob = _this.hexToBlob(img.hex, img.mimeType); + + _this.updateUploadProgressNotification(i, 'uploading'); + + // 2. 【核心优化】直接上传这个 Blob 文件 + // uploadSingleImage 内部需要改用 FormData 发送 + await _this.uploadSingleImage(imageBlob, i); + + _this.updateUploadProgressNotification(i, 'success'); +} catch (err) { console.error('Upload failed:', err); _this.updateUploadProgressNotification(i, 'fail', err.message || 'Network error'); // 上传失败 } @@ -895,8 +902,15 @@ console.log('Processed content:', content); // 输出处理后的内容 }; } }, - // 提取 Word 文件中的表格 - + + hexToBlob(hex, mimeType) { + const len = hex.length / 2; + const bytes = new Uint8Array(len); + for (let i = 0; i < len; i++) { + bytes[i] = parseInt(hex.substr(i * 2, 2), 16); + } + return new Blob([bytes], { type: mimeType }); +}, updateTableStyles(container) { var html = this.$commonJS.updateTableStyles(container, this.typesettingType); var editor = window.tinymce.activeEditor; // 将外部 DOM 内容更新到编辑器 diff --git a/src/components/page/components/table/DynamicTable.vue b/src/components/page/components/table/DynamicTable.vue index a900d14..5e06a47 100644 --- a/src/components/page/components/table/DynamicTable.vue +++ b/src/components/page/components/table/DynamicTable.vue @@ -39,7 +39,7 @@

- +
@@ -71,7 +71,7 @@ export default { }; }, methods: { - async open(type, item) { + async open(type, item,isNoBg) { this.visible = true; this.type = type; @@ -86,7 +86,7 @@ export default { ...item.table, tableHeader: processed.tableHeader, tableContent: processed.tableContent, - oddRowIds: processed.oddRowIds + oddRowIds: isNoBg ? [] : processed.oddRowIds } }); } catch (err) { diff --git a/src/components/page/components/table/word.vue b/src/components/page/components/table/word.vue index a9d2a91..9262682 100644 --- a/src/components/page/components/table/word.vue +++ b/src/components/page/components/table/word.vue @@ -1796,19 +1796,17 @@ export default { this.$refs.proofreadingContent.getTinymceContent('proofreadingContent'); }, 2000), convertSpacesToNbsp(html) { - // 把字符串转成 DOM 结构 + const parser = new DOMParser(); const doc = parser.parseFromString(`
${html}
`, 'text/html'); // 包一层防止body空 - // 遍历所有文本节点 + const walker = doc.createTreeWalker(doc.body, NodeFilter.SHOW_TEXT); let node; while ((node = walker.nextNode())) { const text = node.nodeValue; - // 转换逻辑: - // 1. 保留开头和结尾空格 - // 2. 保留中间连续空格 + const replaced = text .replace(/(^ +)|( +$)/g, (m) => '\u00A0'.repeat(m.length)) // 首尾空格 .replace(/ {2,}/g, (m) => '\u00A0'.repeat(m.length)); // 中间多个空格 @@ -1828,14 +1826,14 @@ export default { }; this.editProofreadingContentVisible = true; - // this.currentId = data.am_id; + }, async handleClickProofreading(data, index) { await this.goToListComment(data); }, - // 让右侧相同 id 的项闪一下边框,并可选滚动到可视区域 + flashRightPanel(proofreadingId, { scroll = true, offset = 80 } = {}) { - // 1️⃣ 清除所有其他元素的闪烁样式 + const oldFlashEls = document.querySelectorAll('.flash-border'); oldFlashEls.forEach((el) => { el.classList.remove('flash-border'); @@ -1849,18 +1847,18 @@ export default { } }); - // 2️⃣ 获取当前匹配元素 + const items = document.querySelectorAll(`[data-proofreading-id="${proofreadingId}"]`); if (!items.length) return; items.forEach((el) => { - // 3️⃣ 确保能重新触发动画 + el.classList.remove('flash-border'); - // 强制重排 + void el.offsetWidth; el.classList.add('flash-border'); - // 4️⃣ 动画结束时自动清除 + el.__flashHandler = (evt) => { if (evt.animationName === 'flashFade') { el.classList.remove('flash-border'); @@ -1870,7 +1868,6 @@ export default { }; el.addEventListener('animationend', el.__flashHandler); - // 5️⃣ 兜底 2 秒后清除 el.__flashTimer = setTimeout(() => { el.classList.remove('flash-border'); if (el.__flashHandler) { @@ -1880,8 +1877,7 @@ export default { el.__flashTimer = null; }, 1000); - // 6️⃣ 自动滚动 - // if (scroll) this.scrollContainerTo(el, offset); + }); }, scrollContainerTo(targetEl, offset = 80) { @@ -1930,7 +1926,7 @@ export default { }); }, openMenu(event, type, currentId) { - console.log('event at line 860:', event); + // 获取鼠标点击位置 this.menuPosition.x = event.clientX + 30; this.menuPosition.y = event.clientY + 40; @@ -2036,7 +2032,7 @@ export default { const range = selection.getRangeAt(0); let selectedNode = range.commonAncestorContainer; - // **向上查找包含 main-id 的最近 div** + let outerDiv = selectedNode; while (outerDiv && outerDiv.tagName !== 'DIV') { outerDiv = outerDiv.parentNode; @@ -2047,27 +2043,24 @@ export default { return; } - // **缓存 main-id 和 type** + this.cachedMainId = outerDiv.getAttribute('main-id'); this.cachedType = outerDiv.getAttribute('type'); - // **创建临时 div 存放选中的 HTML** + const tempDiv = document.createElement('div'); tempDiv.appendChild(range.cloneContents()); - // **检查是否包含 标签** + if (tempDiv.querySelector('img')) { this.clearCache(); this.$message.error(this.$t('commonTable.selectComment')); return; } - - // **获取纯文本** let selectedText = tempDiv.innerText.trim().replace(/\s+/g, ' '); - // **允许保留的 HTML 标签** + 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())) { @@ -2078,19 +2071,19 @@ export default { let preservedContent = Array.from(tempDiv.childNodes).map(preserveTags).join(''); - // **检查是否已有批注** + if (tempDiv.querySelector('.positionRemarkIndex')) { this.clearCache(); this.$message.error(this.$t('commonTable.alreadyCommented')); return; } - // **缓存选区内容** + this.cachedHtml = preservedContent; this.cachedText = selectedText; }, - // **点击“添加批注”按钮** + handleSelection() { if (!this.cachedText) { this.$message.error(this.$t('commonTable.selectComment')); diff --git a/src/components/page/components/table/wordHtml.vue b/src/components/page/components/table/wordHtml.vue index b5a7c96..c2fb4b2 100644 --- a/src/components/page/components/table/wordHtml.vue +++ b/src/components/page/components/table/wordHtml.vue @@ -35,7 +35,7 @@ Figure {{ index + 1 }} @@ -149,7 +149,7 @@
Table {{ index + 1 }} - +
+