diff --git a/scripts/h1-before.xml b/scripts/h1-before.xml new file mode 100644 index 0000000..82130ba --- /dev/null +++ b/scripts/h1-before.xml @@ -0,0 +1 @@ +Introduction \ No newline at end of file diff --git a/scripts/test-h1-inject.docx b/scripts/test-h1-inject.docx new file mode 100644 index 0000000..fad946f Binary files /dev/null and b/scripts/test-h1-inject.docx differ diff --git a/scripts/test-h1-out.docx b/scripts/test-h1-out.docx new file mode 100644 index 0000000..b493f9c Binary files /dev/null and b/scripts/test-h1-out.docx differ diff --git a/scripts/test-h1-out.xml b/scripts/test-h1-out.xml new file mode 100644 index 0000000..e69de29 diff --git a/scripts/test-h1-output.docx b/scripts/test-h1-output.docx new file mode 100644 index 0000000..76b25ba Binary files /dev/null and b/scripts/test-h1-output.docx differ diff --git a/scripts/test-ref-html-export.mjs b/scripts/test-ref-html-export.mjs new file mode 100644 index 0000000..850367c --- /dev/null +++ b/scripts/test-ref-html-export.mjs @@ -0,0 +1,77 @@ +import { JSDOM } from 'jsdom'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath, pathToFileURL } from 'url'; + +const dom = new JSDOM(''); +global.window = dom.window; +global.document = dom.window.document; +global.DOMParser = dom.window.DOMParser; +global.Node = dom.window.Node; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.join(__dirname, '..'); + +const { parseReferencesBulkContent, parseReferencesEditableHtml } = await import( + pathToFileURL(path.join(root, 'src/utils/manuscriptReferenceHtml.js')).href +); +const { buildReferenceRunsFromContentHtml, buildManuscriptWordDocument } = await import( + pathToFileURL(path.join(root, 'src/utils/exportManuscriptWord.js')).href +); +const { Packer } = await import('docx'); + +const sampleItem = + 'Author A. Title one. Journal One. 2020;1:1-10. Available at:
https://doi.org/10.1000/one'; + +const bulkBr = '1. ' + sampleItem + '

2. ' + sampleItem.replace('Author A', 'Author B').replace('one', 'two'); + +const bulkDiv = + '
1. ' + + sampleItem + + '
2. ' + + sampleItem.replace('Author A', 'Author B').replace('one', 'two') + + '
'; + +const bulkEditableDiv = + '
1. ' + + sampleItem + + '

2. ' + + sampleItem.replace('Author A', 'Author B').replace('one', 'two') + + '
'; + +function assertCount(label, items, expected) { + const ok = items.length === expected; + console.log((ok ? 'OK' : 'FAIL') + ' ' + label + ': got ' + items.length + ', expected ' + expected); + if (!ok) { + items.forEach(function (item, i) { + console.log(' [' + i + ']', String(item.html || '').slice(0, 120)); + }); + } + return ok; +} + +console.log('--- parseReferencesBulkContent br/br ---'); +assertCount('br/br', parseReferencesBulkContent(bulkBr), 2); + +console.log('--- parseReferencesBulkContent div/div ---'); +assertCount('div/div', parseReferencesBulkContent(bulkDiv), 2); + +console.log('--- parseReferencesBulkContent editable div ---'); +assertCount('editable div', parseReferencesBulkContent(bulkEditableDiv), 2); + +console.log('--- buildReferenceRunsFromContentHtml ---'); +const runs = buildReferenceRunsFromContentHtml(sampleItem, 'journal'); +console.log('runs:', runs.length, runs.map((r) => r.options?.text || r.text || r.options?.children).join('|')); + +console.log('--- buildManuscriptWordDocument with html items ---'); +const doc = buildManuscriptWordDocument( + [{ type: 0, content: '

Intro text.

' }], + '', + [], + null, + null, + parseReferencesBulkContent(bulkBr) +); +const buf = await Packer.toBuffer(doc); +fs.writeFileSync(path.join(__dirname, 'test-ref-html-export.docx'), buf); +console.log('wrote test-ref-html-export.docx', buf.length, 'bytes'); diff --git a/src/api/index.js b/src/api/index.js index 525753d..487fb66 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 0c73a2b..9724aca 100644 --- a/src/common/js/commonJS.js +++ b/src/common/js/commonJS.js @@ -6,7 +6,7 @@ import mammoth from "mammoth"; import { MathfieldElement } from 'mathlive'; import 'mathlive/dist/mathlive-static.css'; import 'mathlive/dist/mathlive-fonts.css'; -import { importWordDocumentWithMath as parseWordDocumentWithMath, parseHtmlToLatex as convertHtmlToLatex } from '@/utils/wordMathImport'; +import { importWordDocumentWithMath as parseWordDocumentWithMath, parseHtmlToLatex as convertHtmlToLatex, postProcessImportedWordHtml, parseImportedHtmlToContentRows, mergeAdjacentBlueTags, normalizeSpacesAroundBlueTags } from '@/utils/wordMathImport'; import api from '../../api/index.js'; import Common from '@/components/common/common' import Tiff from 'tiff.js'; @@ -1518,6 +1518,9 @@ str = str.replace(regex, function (match, content, offset, fullString) { if (type == 'table' && tag == 'img' && (attrName === "src" || attrName === "width" || attrName === "height")) { return attrMatch; } + if (tag === 'img' && (attrName === 'src' || attrName === 'width' || attrName === 'height' || attrName === 'alt')) { + return attrMatch; + } return ''; }); @@ -1527,7 +1530,7 @@ str = str.replace(regex, function (match, content, offset, fullString) { if (type == 'table') { inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|img|myfigure|mytable|myh3))[^>]+>/g, ''); // 删除不需要的标签 } else { - inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|myfigure|mytable|myh3))[^>]+>/g, ''); // 删除不需要的标签 + inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|myfigure|mytable|myh3|img))[^>]+>/g, ''); // 删除不需要的标签 } @@ -1543,8 +1546,8 @@ str = str.replace(regex, function (match, content, offset, fullString) { }, - importWordDocumentWithMath(file) { - return parseWordDocumentWithMath(file); + importWordDocumentWithMath(file, options) { + return parseWordDocumentWithMath(file, options); }, parseHtmlToLatex(html) { @@ -1552,42 +1555,35 @@ str = str.replace(regex, function (match, content, offset, fullString) { }, cleanAndParseWordContent(content) { - // 1️⃣ 解析成

段落数组 - let tempDiv = document.createElement('div'); - tempDiv.innerHTML = content; // 解析 HTML 内容 - let paragraphs = tempDiv.querySelectorAll("p"); // 选取所有

作为数据项 + const rows = parseImportedHtmlToContentRows(content); + const parsedData = []; - // 2️⃣ 将

内容转换为数组,并处理内容 - let parsedData = Array.from(paragraphs).map(p => { - let text = p.innerHTML.trim(); // 获取内容,去除两端空格 - text = replaceNegativeSign(text); + rows.forEach((raw) => { + const rawStr = String(raw || ''); + if (rawStr && (/wordTableHtml/i.test(rawStr) || /]/i.test(rawStr))) { + parsedData.push(/wordTableHtml/i.test(rawStr) ? rawStr : `

${rawStr}
`); + return; + } + if (rawStr === '') { + parsedData.push(''); + return; + } - text = this.transformHtmlString(text) - // 3️⃣ **正确移除 (Word 复制的无效标签)** - text = text.replace(/<\/?o:p[^>]*>/g, ""); - - // 4️⃣ **移除所有 style="..."** - text = text.replace(/\s*style="[^"]*"/gi, ""); - - // 5️⃣ **修正标签替换** - text = text.replace(//gi, "").replace(/<\/strong>/gi, ""); - text = text.replace(//gi, "").replace(/<\/em>/gi, ""); - - // 6️⃣ **移除空的 span、b、i 标签** - text = text.replace(/\s*<\/span>/gi, ""); - text = text.replace(/\s*<\/b>/gi, ""); - text = text.replace(/\s*<\/i>/gi, ""); - - // 7️⃣ **确保不移除半个标签(修复匹配规则)** - text = text.replace(/<[^\/>]+>\s*<\/[^>]+>/gi, match => { - return match.trim() === "" ? "" : match; - }); - - // 8️⃣ **返回最终内容** - return text.trim() === "" ? "" : text; + let text = replaceNegativeSign(rawStr.trim()); + text = this.transformHtmlString(text); + text = text.replace(/<\/?o:p[^>]*>/g, ''); + text = text.replace(/\s*style="[^"]*"/gi, ''); + text = text.replace(//gi, '').replace(/<\/strong>/gi, ''); + text = text.replace(//gi, '').replace(/<\/em>/gi, ''); + text = text.replace(/\s*<\/span>/gi, ''); + text = text.replace(/\s*<\/b>/gi, ''); + text = text.replace(/\s*<\/i>/gi, ''); + text = text.replace(/<[^\/>]+>\s*<\/[^>]+>/gi, (match) => (match.trim() === '' ? '' : match)); + text = mergeAdjacentBlueTags(text); + text = normalizeSpacesAroundBlueTags(text); + parsedData.push(text.trim() === '' ? '' : text); }); - return parsedData; }, @@ -2865,12 +2861,12 @@ str = str.replace(regex, function (match, content, offset, fullString) { background: 'rgba(0, 0, 0, 0.45)' }) : null; - parseWordDocumentWithMath(file) + parseWordDocumentWithMath(file, { textOnly: true }) .then((html) => { if (!html) { throw new Error('empty content'); } - ed.insertContent(html); + ed.setContent(html); const body = ed.getBody(); body.querySelectorAll('wmath').forEach((el) => { let latex = (el.getAttribute('data-latex') || '').trim(); diff --git a/src/components/common/common.vue b/src/components/common/common.vue index 9bac70a..a1781ec 100644 --- a/src/components/common/common.vue +++ b/src/components/common/common.vue @@ -2,14 +2,14 @@ //记得切换 //正式 -const mediaUrl = '/public/'; -const baseUrl = '/'; +// const mediaUrl = '/public/'; +// const baseUrl = '/'; //正式环境 -// const mediaUrl = 'https://submission.tmrjournals.com/public/'; -// // const mediaUrl = 'http://zmzm.tougao.dev.com/public/'; -// const baseUrl = '/api' +const mediaUrl = 'https://submission.tmrjournals.com/public/'; +// const mediaUrl = 'http://zmzm.tougao.dev.com/public/'; +const baseUrl = '/api' //测试环境 diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js index d1800fc..bba3675 100644 --- a/src/components/common/langs/en.js +++ b/src/components/common/langs/en.js @@ -382,7 +382,9 @@ const en = { unsubscribeSwitchOff: 'Subscribed', unsubscribeMissingId: 'Missing expert ID, unable to switch unsubscribe status', unsubscribeUpdateSuccess: 'Unsubscribe status updated', - unsubscribeUpdateFailed: 'Failed to update unsubscribe status' + unsubscribeUpdateFailed: 'Failed to update unsubscribe status', + lastContactTime: 'Last contact time: {time}', + lastContactTimeEmpty: 'No contact record' }, countryManagement: { title: 'Country Management', @@ -1146,10 +1148,12 @@ const en = { AnnotationList: 'Annotation List', Annotations: 'Comments', exportWord: 'Generate Word file', + exportWordShort: 'Word', exportManuscriptEmpty: 'No content to export.', exportManuscriptSuccess: 'Word downloaded.', exportManuscriptFail: 'Failed to export Word.', citeRelevanceDetect: 'Download relevance HTML', + citeRelevanceDetectShort: 'Relevance', citeRelevanceTitle: 'Citation relevance check', citeRelevanceTitleProgress: 'Citation relevance ({current}/{total})', citeRelevanceEmpty: 'No citations to review.', @@ -1182,8 +1186,34 @@ const en = { citeRelevanceCopyGroup: 'Copy paragraph', citeRelevanceDownloadHtml: 'Download HTML', citeRelevanceDownloadHtmlSuccess: 'HTML downloaded', + refHtmlCopy: 'Copy references', + refHtmlCopyShort: 'Copy', + refHtmlCopySuccess: 'References copied (numbering, italic journal, blue DOI, Available at line break)', + parseBookReference: 'Parse Book', + parseBookReferenceSuccess: 'Book fields parsed successfully', + parseBookReferenceEmpty: 'Could not parse book from Content. Expected: Author. Title. Publication Details. ISBN', + parseBookReferenceContentEmpty: 'Content cannot be empty', refHtmlDownload: 'Download references HTML', - refHtmlDownloadSuccess: 'References HTML downloaded', + refHtmlDownloadShort: 'Ref HTML', + refHtmlDownloadSuccess: 'References HTML downloaded. After editing, click "Upload references" then "Generate Word file".', + refHtmlUpload: 'Upload references', + refHtmlUploadShort: 'Upload', + refHtmlUploadSuccess: '{n} reference(s) loaded. Click "Generate Word file".', + refHtmlUploadConfirm: 'Confirm load', + refHtmlUploadLoadedTip: '{n} reference(s) loaded — click "Generate Word file" to export', + exportWordWithUploadedRefs: 'Generate Word with {n} uploaded reference(s)', + exportWordWithUploadedRefsSuccess: 'Word generated with {n} uploaded reference(s)', + refHtmlPaste: 'Paste references', + refHtmlPasteTitle: 'Paste full references', + refHtmlPasteTip: 'Paste the complete references below — no need to edit one by one. Separate entries with blank lines or numbering (1. 2. …); HTML tags such as are supported.', + refHtmlPastePlaceholder: 'Paste full references here…', + refHtmlPasteParsed: '{n} reference(s) detected', + refHtmlPasteParseEmpty: 'No references detected. Use the format from "Copy references" (1. 2. numbering + Available at line break).', + refHtmlPasteExport: 'Export Word', + refHtmlPasteCancel: 'Cancel', + refHtmlImportHtml: 'Import HTML file', + refHtmlImportSuccess: 'Imported into the editor. Click "Export Word" when ready.', + refHtmlBulkHint: 'Edit all references as one block, or paste AI output here. Separate entries with blank lines or 1. 2. numbering.', refHtmlToWord: 'References HTML to Word', refHtmlToWordSuccess: 'Word exported with edited references', refHtmlToWordFail: 'Failed to convert references HTML to Word', @@ -1192,8 +1222,8 @@ const en = { refHtmlParseEmpty: 'No reference entries found in HTML. Please use the HTML downloaded from this system.', refHtmlPageTitle: 'References editor', refHtmlReferencesTitle: 'References', - refHtmlIntro: 'Edit each reference directly, or paste AI output (supports italics and other HTML). When done, click "Download edited HTML", then use "References HTML to Word" on the typesetting page.', - refHtmlSaveTip: 'Tip: save the HTML after editing, then import it on the typesetting page to export Word.', + refHtmlIntro: 'Download to edit all references in one block, or paste AI output (supports italics and other HTML). Save the HTML when done, or use "Paste references" on the typesetting page to export Word.', + refHtmlSaveTip: 'Tip: save the HTML after editing the full block, or copy the content and use "Paste references" on the typesetting page.', refHtmlDownloadEdited: 'Download edited HTML', refHtmlDownloadFileName: 'references-edited.html', exportImg: 'Export PNG', @@ -1264,6 +1294,8 @@ const en = { Row: "Row", addRow: "Add Row", Uncheck: 'Uncheck the paragraph', + selectAll: 'Select All', + selectAllShort: 'All', ManuscirptAIProofreading: 'Manuscript AI Proofreading', AIProofreading: 'AI Proofreading', Association: 'Association', diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js index ec125ea..05e83dd 100644 --- a/src/components/common/langs/zh.js +++ b/src/components/common/langs/zh.js @@ -371,7 +371,9 @@ const zh = { unsubscribeSwitchOff: '已订阅', unsubscribeMissingId: '缺少专家 ID,无法切换退订状态', unsubscribeUpdateSuccess: '退订状态更新成功', - unsubscribeUpdateFailed: '退订状态更新失败' + unsubscribeUpdateFailed: '退订状态更新失败', + lastContactTime: '最近一次联系时间:{time}', + lastContactTimeEmpty: '暂无联系记录' }, countryManagement: { title: '国家信息维护', @@ -1132,10 +1134,12 @@ const zh = { AnnotationList: '批注列表', Annotations: '批注', exportWord: '生成 Word 文件', + exportWordShort: 'Word', exportManuscriptEmpty: '没有可导出的内容。', exportManuscriptSuccess: 'Word 已下载。', exportManuscriptFail: 'Word 导出失败。', citeRelevanceDetect: '下载相关性 HTML', + citeRelevanceDetectShort: '相关性', citeRelevanceTitle: '引文相关性核查', citeRelevanceTitleProgress: '引文相关性核查 ({current}/{total})', citeRelevanceEmpty: '当前没有可核查的引文。', @@ -1168,8 +1172,34 @@ const zh = { citeRelevanceCopyGroup: '复制本段', citeRelevanceDownloadHtml: '下载 HTML', citeRelevanceDownloadHtmlSuccess: 'HTML 已下载', + refHtmlCopy: '复制参考文献', + refHtmlCopyShort: '复制', + refHtmlCopySuccess: '参考文献已复制(含编号、期刊斜体、蓝色 DOI、Available at 换行)', + parseBookReference: '解析书籍', + parseBookReferenceSuccess: '书籍字段已按规则解析', + parseBookReferenceEmpty: '无法从 Content 中解析书籍信息,请检查格式(Author. Title. Publication Details. ISBN)', + parseBookReferenceContentEmpty: 'Content 不能为空', refHtmlDownload: '下载参考文献 HTML', - refHtmlDownloadSuccess: '参考文献 HTML 已下载', + refHtmlDownloadShort: 'Ref HTML', + refHtmlDownloadSuccess: '参考文献 HTML 已下载,改完后请点「上传参考文献」再点「生成 Word 文件」', + refHtmlUpload: '上传参考文献', + refHtmlUploadShort: '上传', + refHtmlUploadSuccess: '已加载 {n} 条参考文献,请点击「生成 Word 文件」', + refHtmlUploadConfirm: '确认加载', + refHtmlUploadLoadedTip: '已加载 {n} 条参考文献,点击「生成 Word 文件」导出', + exportWordWithUploadedRefs: '将使用已上传的 {n} 条参考文献生成 Word', + exportWordWithUploadedRefsSuccess: 'Word 已生成(含上传的 {n} 条参考文献)', + refHtmlPaste: '粘贴参考文献', + refHtmlPasteTitle: '粘贴完整参考文献', + refHtmlPasteTip: '将整段参考文献粘贴到下方即可,无需逐条修改。支持 1. 2. 编号分隔,或条目之间空一行;也支持 斜体等 HTML 标签。', + refHtmlPastePlaceholder: '在此粘贴完整参考文献内容…', + refHtmlPasteParsed: '已识别 {n} 条参考文献', + refHtmlPasteParseEmpty: '暂未识别到参考文献,请使用「复制参考文献」的格式(1. 2. 编号 + Available at 换行)', + refHtmlPasteExport: '导出 Word', + refHtmlPasteCancel: '取消', + refHtmlImportHtml: '从 HTML 文件导入', + refHtmlImportSuccess: '已导入到编辑框,确认后点击「导出 Word」', + refHtmlBulkHint: '以下为整段参考文献,可整体替换或粘贴 AI 返回的完整内容;条目之间用空行或 1. 2. 编号分隔。', refHtmlToWord: '参考文献 HTML 转 Word', refHtmlToWordSuccess: 'Word 已导出(含编辑后的参考文献)', refHtmlToWordFail: '参考文献 HTML 转 Word 失败', @@ -1178,8 +1208,8 @@ const zh = { refHtmlParseEmpty: 'HTML 中未找到参考文献条目,请使用本系统下载的参考文献 HTML', refHtmlPageTitle: '参考文献编辑', refHtmlReferencesTitle: 'References', - refHtmlIntro: '可直接编辑每条文献,或粘贴 AI 返回的内容(支持 斜体等 HTML 标签)。编辑完成后点击「下载编辑后的 HTML」,再在排版页使用「参考文献 HTML 转 Word」。', - refHtmlSaveTip: '提示:编辑后请点击上方按钮保存 HTML;也可在排版页导入该文件导出 Word。', + refHtmlIntro: '下载后可在一个大文本框里整体编辑全部参考文献,或粘贴 AI 返回的完整内容(支持 斜体等 HTML 标签)。编辑完成后保存 HTML,或在排版页使用「粘贴参考文献」导出 Word。', + refHtmlSaveTip: '提示:整段编辑后点击上方按钮保存 HTML;也可复制内容后在排版页「粘贴参考文献」导出 Word。', refHtmlDownloadEdited: '下载编辑后的 HTML', refHtmlDownloadFileName: 'references-edited.html', exportImg: '导出 图片', @@ -1249,6 +1279,8 @@ const zh = { Row:"空行", addRow:"新增空行", Uncheck:'取消勾选段落', + selectAll: '全选', + selectAllShort: '全选', ManuscirptAIProofreading:'稿件AI校对', AIProofreading:'AI校对', Association:'关联', diff --git a/src/components/page/GenerateCharts.vue b/src/components/page/GenerateCharts.vue index 5ac6a44..6a93989 100644 --- a/src/components/page/GenerateCharts.vue +++ b/src/components/page/GenerateCharts.vue @@ -416,7 +416,8 @@ type="content" @openLatexEditor="openLatexEditor" v-if="addContentVisible" - :enable-import-word-math="true" + :enable-import-word-math="zyModeEnabled" + :article-id="articleId" ref="addContent" style="margin-left: -115px" > @@ -445,6 +446,7 @@ import { LATEX_DATA_HTML_DATA, MATH_FORMULA_TABLE_TITLE } from '@/utils/mathForm import Tinymce from '@/components/page/components/Tinymce'; import bottomTinymce from '@/components/page/components/Tinymce'; import catalogue from '@/components/page/components/table/catalogue.vue'; +import { isZyModeEnabled } from '@/utils/zyMode'; export default { data() { return { @@ -578,6 +580,9 @@ export default { catalogue }, computed: { + zyModeEnabled() { + return isZyModeEnabled(this.$route); + }, combinedValue() { // 将两个值组合成一个新的值,可以是字符串、数组、对象等 // return `${this.isFirstComponentLoaded}-${this.isWordComponentLoaded}`; @@ -726,17 +731,7 @@ export default { this.saveContent(content, this.currentContent.am_id); } else if (type == 'addcontent') { - var hasTable = /[\s\S]*?<\/table>/i.test(content); - - if (hasTable) { - this.$message({ - type: 'warning', - message: 'Table content is not supported!' - }); - return false; - } var list = this.$commonJS.cleanAndParseWordContent(content); - this.saveContentList(list, this.currentId); } else if (type == 'table') { diff --git a/src/components/page/OnlineProofreading.vue b/src/components/page/OnlineProofreading.vue index 8c1c9c5..00da340 100644 --- a/src/components/page/OnlineProofreading.vue +++ b/src/components/page/OnlineProofreading.vue @@ -726,17 +726,7 @@ export default { this.saveContent(content, this.currentContent.am_id); } else if (type == 'addcontent') { - var hasTable = /[\s\S]*?<\/table>/i.test(content); - - if (hasTable) { - this.$message({ - type: 'warning', - message: 'Table content is not supported!' - }); - return false; - } var list = this.$commonJS.cleanAndParseWordContent(content); - this.saveContentList(list, this.currentId); } else if (type == 'table') { diff --git a/src/components/page/articleListEditor_B1.vue b/src/components/page/articleListEditor_B1.vue index ce652b1..88ed8dd 100644 --- a/src/components/page/articleListEditor_B1.vue +++ b/src/components/page/articleListEditor_B1.vue @@ -1182,7 +1182,7 @@ import { getAuthorDisplayName, getAuthorAffiliationPreview } from '@/utils/productionSubmissionImport'; -import { isZyModeEnabled } from '@/utils/zyMode'; +import { isZyModeEnabled, isZySkipCheckEnabled } from '@/utils/zyMode'; export default { data() { return { @@ -2892,39 +2892,19 @@ export default { // 6----创建文章 EstaBlish() { + if (isZySkipCheckEnabled(this.$route)) { + this.doTypeSettingNewDirect(); + return; + } this.$api .post('api/Production/checkRefer', { p_article_id: this.p_article_id }) .then((res) => { if (res.code == 0) { - const loading = this.$loading({ - lock: true, - text: 'Loading...', - spinner: 'el-icon-loading', - background: 'rgba(0, 0, 0, 0.7)' - }); - this.$api - .post('api/Production/doTypeSettingNew', { - article_id: this.detailMes.article_id - }) - .then((res) => { - if (res.code == 0) { - this.getWorldPdf(); - this.$message.success('Successfully generated manuscript!'); - loading.close(); - } else { - this.$message.error(res.msg); - loading.close(); - } - }) - .catch((err) => { - this.$message.error(err); - loading.close(); - }); + this.doTypeSettingNewDirect(); } else { this.$message.error(res.msg); - return; } }) .catch((err) => { @@ -2932,6 +2912,32 @@ export default { }); }, + doTypeSettingNewDirect() { + const loading = this.$loading({ + lock: true, + text: 'Loading...', + spinner: 'el-icon-loading', + background: 'rgba(0, 0, 0, 0.7)' + }); + this.$api + .post('api/Production/doTypeSettingNew', { + article_id: this.detailMes.article_id + }) + .then((res) => { + if (res.code == 0) { + this.getWorldPdf(); + this.$message.success('Successfully generated manuscript!'); + } else { + this.$message.error(res.msg); + } + loading.close(); + }) + .catch((err) => { + this.$message.error(err); + loading.close(); + }); + }, + // 6----校对文章 htmlContet() { this.$api diff --git a/src/components/page/components/Tinymce/index.vue b/src/components/page/components/Tinymce/index.vue index b654f92..3850429 100644 --- a/src/components/page/components/Tinymce/index.vue +++ b/src/components/page/components/Tinymce/index.vue @@ -11,6 +11,7 @@ import { Document, Packer, PageOrientation, Paragraph, TextRun } from 'docx'; // import html2canvas from 'html2canvas'; import { extractHexImagesFromRTF, hexToBlob } from '@/utils/rtfParser'; import { tableStyle } from '@/utils/tinymceStyles'; +import { normalizeEditorPasteHtml, isStructuredDataTable, normalizeSpacesAroundBlueTags } from '@/utils/wordMathImport'; export default { name: 'tinymce', components: {}, @@ -63,6 +64,11 @@ export default { }, articleId: { default: '' + }, + /** Word 批量导入:保留 base64 图片,不自动上传 */ + keepInlineImages: { + type: Boolean, + default: false } }, data() { @@ -698,6 +704,8 @@ export default { plugins: 'texttransform noneditable table image', // 启用 forecolor 和 code 插件 // plugins: 'forecolor code paste table image mathType searchreplace raw', // 启用 forecolor 和 code 插件 end_container_on_empty_block: true, + automatic_uploads: !_this.keepInlineImages, + paste_data_images: true, content_css: 'default', // 加载 TinyMCE 默认样式表 mathjax: { // 配置 MathJax 用于渲染数学公式 @@ -707,6 +715,19 @@ export default { // automatic_uploads: false, images_upload_handler: function (blobInfo, progress) { return new Promise((resolve, reject) => { + const inlineUri = blobInfo.blobUri ? blobInfo.blobUri() : ''; + if (_this.keepInlineImages && inlineUri && /^data:/i.test(inlineUri)) { + resolve(inlineUri); + return; + } + if (!_this.articleId) { + if (inlineUri) { + resolve(inlineUri); + return; + } + reject('Upload Error: Please select an article'); + return; + } const xhr = new XMLHttpRequest(); const formData = new FormData(); const file = blobInfo.blob(); @@ -926,23 +947,28 @@ export default { e.content = e.content.replace(//g, '').replace(/<\/i>/g, ''); }); }, - paste_preprocess: function (plugin, args) { + paste_preprocess: function (editor, args) { let imgIdx = 0; const silentPlaceholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7'; - let content = args.content.replace(/(]*?)src="file:\/\/\/[^" ]*"/gi, (match, p1) => { - // 保留 data-idx, Word 里的尺寸 + let content = String(args.content || '').replace(/(]*?)src="file:\/\/\/[^" ]*"/gi, (match, p1) => { return `${p1}src="${silentPlaceholder}" class="word-img-placeholder" data-idx="${imgIdx++}"`; }); let tempDiv = document.createElement('div'); tempDiv.innerHTML = content; - if (tempDiv.querySelector('table')) { - if (_this.type == 'table') { - - _this.$commonJS.parseTableToArray(content, (tableList) => { - var contentHtml = ` + const tableEl = tempDiv.querySelector('table'); + const isRealDataTable = + tableEl && + _this.type === 'table' && + _this.isAutomaticUpdate && + isStructuredDataTable(tableEl); + + if (isRealDataTable) { + _this.$commonJS.parseTableToArray(content, (tableList) => { + if (!tableList || !tableList.length) return; + const contentHtml = `
${tableList @@ -965,12 +991,10 @@ export default {
`; - - const container = document.createElement('div'); - container.innerHTML = contentHtml; - args.content = container.innerHTML; // 更新处理后的内容 - }); - } + const container = document.createElement('div'); + container.innerHTML = contentHtml; + args.content = normalizeEditorPasteHtml(container.innerHTML); + }); } else { const plainText = (tempDiv.textContent || tempDiv.innerText || '').trim(); const builtPlain = _this.buildWmathHtmlFromLatexText(plainText); @@ -987,18 +1011,18 @@ export default { } } - if (args.event) { - args.event.preventDefault(); - args.event.stopPropagation(); - } - - if (_this.isAutomaticUpdate) { - args.content = _this.$commonJS.transformHtmlString(content); // 更新处理后的内容 - } else { - args.content = content; + if (!isRealDataTable) { + const normalized = normalizeEditorPasteHtml(content); + if (_this.isAutomaticUpdate) { + args.content = _this.$commonJS.transformHtmlString(normalized); + } else { + args.content = normalized; + } } setTimeout(() => { - window.renderMathJax(_this.tinymceId); + if (typeof window.renderMathJax === 'function') { + window.renderMathJax(_this.tinymceId); + } }, 10); }, clear_custom_action: (editor, vm) => { @@ -1101,7 +1125,11 @@ export default { content = content.replace(/]*>/g, '').replace(/<\/span>/g, ''); // 去除span标签 content = content.replace(//g, '').replace(/<\/strong>/g, ''); content = content.replace(//g, '').replace(/<\/em>/g, ''); - content = content.replace(/ /g, ' '); // 将所有   替换为空格 + if (type === 'addcontent') { + content = normalizeSpacesAroundBlueTags(content); + } else { + content = content.replace(/ /g, ' '); + } this.$emit('getContent', type, content); }, diff --git a/src/components/page/components/table/content.vue b/src/components/page/components/table/content.vue index 1480991..4ceb47a 100644 --- a/src/components/page/components/table/content.vue +++ b/src/components/page/components/table/content.vue @@ -10,6 +10,8 @@ ref="tinymceChild1" :wordStyle="wordStyle" :isAutomaticUpdate="isAutomaticUpdate" + :article-id="articleId" + :keep-inline-images="enableImportWordMath" @getContent="getContent" @openLatexEditor="openLatexEditor" @updateChange="updateChange" @@ -48,6 +50,9 @@ export default { enableImportWordMath: { type: Boolean, default: false + }, + articleId: { + default: '' } }, components: { @@ -62,9 +67,9 @@ export default { if (!this.isAutomaticUpdate) { groups.push('LateX'); } - // if (this.enableImportWordMath) { - // groups.push('importWordMath'); - // } + if (this.enableImportWordMath) { + groups.push('importWordMath'); + } groups.push( 'myuppercase myuppercasea Line MoreSymbols', 'subscript superscript', diff --git a/src/components/page/components/table/word.vue b/src/components/page/components/table/word.vue index 8279bd0..b4efcec 100644 --- a/src/components/page/components/table/word.vue +++ b/src/components/page/components/table/word.vue @@ -21,6 +21,9 @@ :style="{ '--p-l': '10px', '--p-l': '10px', '--fl-ai': 'center' }" > {{ $t('commonTable.Uncheck') }} + + {{ $t('commonTable.selectAllShort') }} +
  • - - - {{ $t('commonTable.exportWord') }} + + + + + {{ $t('commonTable.refHtmlDownloadShort') }} + + +
  • +
  • + + + + + + {{ $t('commonTable.refHtmlUploadShort') }} + ({{ uploadedReferencesCount }}) + + + +
  • +
  • + + + + + {{ $t('commonTable.exportWordShort') }} + +
  • - - - {{ $t('commonTable.citeRelevanceDetect') }} + + + + + {{ $t('commonTable.citeRelevanceDetectShort') }} + +
  • @@ -1008,6 +1050,43 @@
    + + +

    + {{ $t('commonTable.refHtmlPasteTip') }} +

    + +

    + {{ referencesPastePreviewText }} +

    + + {{ $t('commonTable.refHtmlPasteCancel') }} + {{ $t('commonTable.refHtmlImportHtml') }} + + {{ $t('commonTable.refHtmlUploadConfirm') }} + + +
    +
    0) { + return this.$t('commonTable.refHtmlPasteParsed', { n: this.referencesPasteCount }); + } + return this.$t('commonTable.refHtmlPasteParseEmpty'); + }, sortedProofreadingList() { const order = [2, 1, 3]; const rank = { 2: 0, 1: 1, 3: 2 }; @@ -1496,6 +1601,164 @@ export default { this.editors = {}; }, methods: { + async loadManuscriptReferences(forceReload) { + if (!forceReload && this.manuscriptReferences && this.manuscriptReferences.length) { + return this.manuscriptReferences; + } + if (!this.$api) { + throw new Error('NO_API'); + } + const references = await fetchManuscriptReferenceList(this.$api, this.articleId, this.pArticleId); + this.manuscriptReferences = references || []; + return this.manuscriptReferences; + }, + async handleCopyReferences() { + if (this.referencesCopyLoading) { + return; + } + if (!this.$api) { + this.$message.error(this.$t('commonTable.refHtmlLoadFail')); + return; + } + + this.referencesCopyLoading = true; + try { + const references = await this.loadManuscriptReferences(false); + if (!references.length) { + this.$message.warning(this.$t('commonTable.refHtmlEmpty')); + return; + } + await copyReferencesToClipboard(references); + this.$message.success(this.$t('commonTable.refHtmlCopySuccess')); + } catch (err) { + console.error(err); + if (err && err.message === 'EMPTY') { + this.$message.warning(this.$t('commonTable.refHtmlEmpty')); + } else { + this.$message.error(this.$t('commonTable.refHtmlLoadFail')); + } + } finally { + this.referencesCopyLoading = false; + } + }, + async handleDownloadReferencesHtml() { + if (this.referencesHtmlLoading) { + return; + } + if (!this.$api) { + this.$message.error(this.$t('commonTable.refHtmlLoadFail')); + return; + } + + this.referencesHtmlLoading = true; + try { + const references = await this.loadManuscriptReferences(false); + if (!references.length) { + this.$message.warning(this.$t('commonTable.refHtmlEmpty')); + return; + } + + const labels = buildReferencesHtmlLabels(this.$t.bind(this)); + const fileName = 'references-editor' + (this.articleId ? '-' + this.articleId : '') + '.html'; + downloadReferencesEditableHtml(references, labels, fileName); + this.$message.success(this.$t('commonTable.refHtmlDownloadSuccess')); + } catch (err) { + console.error(err); + this.$message.error(this.$t('commonTable.refHtmlLoadFail')); + } finally { + this.referencesHtmlLoading = false; + } + }, + triggerReferencesUpload() { + const input = this.$refs.referencesHtmlFileInput; + if (input) { + input.value = ''; + input.click(); + } + }, + triggerReferencesHtmlFileImport() { + this.triggerReferencesUpload(); + }, + parseUploadedReferenceContent(text, fileName) { + const raw = String(text || ''); + const isPlainText = /\.txt$/i.test(String(fileName || '')); + if (isPlainText) { + return parseReferencesBulkContent(raw); + } + return parseReferencesEditableHtml(raw); + }, + applyUploadedReferences(items) { + if (!items || !items.length) { + this.$message.warning(this.$t('commonTable.refHtmlParseEmpty')); + return false; + } + this.uploadedReferenceHtmlItems = items; + this.uploadedReferencesCount = items.length; + this.$message.success( + this.$t('commonTable.refHtmlUploadSuccess', { n: items.length }) + ); + return true; + }, + handleReferencesFileUpload(event) { + const input = event && event.target; + const file = input && input.files && input.files[0]; + if (!file) { + return; + } + + this.referencesUploadLoading = true; + const reader = new FileReader(); + reader.onload = () => { + try { + const items = this.parseUploadedReferenceContent(reader.result, file.name); + this.applyUploadedReferences(items); + } catch (err) { + console.error(err); + this.$message.error(this.$t('commonTable.refHtmlToWordFail')); + } finally { + this.referencesUploadLoading = false; + if (input) { + input.value = ''; + } + } + }; + reader.onerror = () => { + this.referencesUploadLoading = false; + if (input) { + input.value = ''; + } + this.$message.error(this.$t('commonTable.refHtmlToWordFail')); + }; + reader.readAsText(file, 'UTF-8'); + }, + async openReferencesPasteDialog() { + if (this.referencesUploadLoading) { + return; + } + if (!this.$api) { + this.$message.error(this.$t('commonTable.refHtmlLoadFail')); + return; + } + + try { + const references = await this.loadManuscriptReferences(false); + this.referencesPasteText = references.length ? buildReferencesCopyText(references) : ''; + } catch (err) { + console.error(err); + this.referencesPasteText = ''; + } + this.updateReferencesPastePreview(); + this.referencesPasteVisible = true; + }, + updateReferencesPastePreview() { + this.referencesPasteCount = countParsedReferences(this.referencesPasteText); + }, + confirmReferencesUpload() { + const items = parseReferencesBulkContent(this.referencesPasteText); + if (this.applyUploadedReferences(items)) { + this.referencesPasteVisible = false; + } + }, async handleExportManuscriptWord() { if (this.exportingManuscriptWord) { return; @@ -1506,19 +1769,32 @@ export default { } this.exportingManuscriptWord = true; try { + const hasUploaded = + this.uploadedReferenceHtmlItems && this.uploadedReferenceHtmlItems.length; await downloadManuscriptWord(this.wordList, this.mediaUrl, 'manuscript', { - fetchReferences: true, + fetchReferences: !hasUploaded, + referenceHtmlItems: hasUploaded ? this.uploadedReferenceHtmlItems : null, apiClient: this.$api, articleId: this.articleId, pArticleId: this.pArticleId }); - this.$message.success(this.$t('commonTable.exportManuscriptSuccess') || 'Word downloaded.'); + this.$message.success( + hasUploaded + ? this.$t('commonTable.exportWordWithUploadedRefsSuccess', { + n: this.uploadedReferencesCount + }) + : this.$t('commonTable.exportManuscriptSuccess') || 'Word downloaded.' + ); } catch (err) { console.error(err); if (err && err.message === 'NO_CONTENT') { this.$message.warning(this.$t('commonTable.exportManuscriptEmpty') || 'No content to export.'); } else { - this.$message.error(this.$t('commonTable.exportManuscriptFail') || 'Failed to export Word.'); + const detail = + err && err.message + ? err.message + : this.$t('commonTable.exportManuscriptFail') || 'Failed to export Word.'; + this.$message.error(detail); } } finally { this.exportingManuscriptWord = false; @@ -1539,11 +1815,7 @@ export default { this.citationRelevanceLoading = true; try { - let references = this.manuscriptReferences; - if (!references || !references.length) { - references = await fetchManuscriptReferenceList(this.$api, this.articleId, this.pArticleId); - this.manuscriptReferences = references || []; - } + const references = await this.loadManuscriptReferences(false); const items = buildCitationReviewQueue(this.wordList, this.manuscriptReferences); if (!items.length) { @@ -2607,6 +2879,14 @@ export default { this.selectedIds = []; this.$forceUpdate(); }, + handleSelectAll() { + this.currentId = null; + this.currentData = {}; + this.selectedIds = (this.wordList || []) + .map((item) => (item && item.am_id != null ? item.am_id : null)) + .filter((id) => id != null); + this.$forceUpdate(); + }, onEdit() { this.$emit('onEdit', this.currentId); }, @@ -4378,6 +4658,52 @@ export default { border-radius: 0; height: auto; } +.HTitleBox li.zy-toolbar-btn { + padding: 4px 10px; + margin: 0 2px; + font-size: 12px !important; + font-weight: normal !important; + min-width: auto; + height: auto; + color: #409eff; + background: #fff; + border: 1px solid #409eff; + border-radius: 4px; + box-sizing: border-box; +} +.HTitleBox li.zy-toolbar-btn:hover { + background: #ecf5ff; +} +.HTitleBox li.zy-toolbar-btn.zy-toolbar-btn--ready { + border-color: #67c23a; + color: #67c23a; +} +.HTitleBox li.zy-toolbar-btn.zy-toolbar-btn--ready:hover { + background: #f0f9eb; +} +.zy-toolbar-btn-inner { + display: inline-flex; + flex-direction: row; + align-items: center; + justify-content: center; + gap: 4px; + line-height: 1.2; + pointer-events: none; +} +.zy-toolbar-btn-inner i { + font-size: 14px; + margin: 0; +} +.zy-toolbar-label { + font-size: 12px; + white-space: nowrap; + font-weight: normal; +} +.zy-toolbar-badge { + font-style: normal; + color: inherit; + font-weight: bold; +} .operateBox { width: auto; display: flex; diff --git a/src/components/page/editPublicRefRdit.vue b/src/components/page/editPublicRefRdit.vue index 2e910a8..7734f6e 100644 --- a/src/components/page/editPublicRefRdit.vue +++ b/src/components/page/editPublicRefRdit.vue @@ -556,6 +556,16 @@ >We have detected updates to the reference content. You need to click the "Automatic parsing" button to recognize them. + + {{ $t('commonTable.parseBookReference') }} +
    @@ -898,6 +908,8 @@