From 82ec4edba98c68382785649d53bd065eaae07222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=A7=8B=E4=BA=8E=E5=88=9D=E8=A7=81?= <752204717@qq.com> Date: Mon, 3 Aug 2026 11:05:44 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=93=E5=88=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/index.js | 4 +- src/common/js/commonJS.js | 88 +++---------------- src/components/common/common.vue | 14 +-- src/components/common/langs/en.js | 3 + src/components/common/langs/zh.js | 3 + src/components/page/GenerateCharts.vue | 82 ++++++++++++++++- src/components/page/OnlineProofreading.vue | 82 ++++++++++++++++- .../components/table/wordHtmlTypesetting.vue | 19 +++- src/utils/wordMathImport.js | 4 + 9 files changed, 212 insertions(+), 87 deletions(-) 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 36c93a9..0b68ae7 100644 --- a/src/common/js/commonJS.js +++ b/src/common/js/commonJS.js @@ -1180,86 +1180,24 @@ export default { callback([]); } }, - handleFileUpload(event, callback) { + async 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(//g); - const validTables = tableContent - ? tableContent.filter(table => //.test(table)) - : []; - - callback(validTables); - }).catch(err => { - console.error('mammoth 转换失败:', err); - }); - }).catch(err => { - console.error("Zip 读取失败:", err); - }); - }; - - reader.readAsArrayBuffer(file); + try { + // 走 OMML→LaTeX 解析,表格单元格内公式转为 + const html = await parseWordDocumentWithMath(file, { forTableExtract: true }); + const tableContent = String(html || '').match(//gi); + const validTables = tableContent + ? tableContent.filter((table) => /]/i.test(table)) + : []; + callback(validTables); + } catch (err) { + console.error('Word 表格导入失败:', err); + callback([]); + } }, // async extractWordTablesToArrays(file, callback) { diff --git a/src/components/common/common.vue b/src/components/common/common.vue index e0f6d02..0e04e69 100644 --- a/src/components/common/common.vue +++ b/src/components/common/common.vue @@ -2,16 +2,16 @@ //记得切换 // //正式 -// const mediaUrl = '/public/'; -// const tmrjournals = 'https://master.tmrjournals.com/public/'; -// const baseUrl = '/'; +const mediaUrl = '/public/'; +const tmrjournals = 'https://master.tmrjournals.com/public/'; +const baseUrl = '/'; //正式环境 -const mediaUrl = 'https://submission.tmrjournals.com/public/'; -// const mediaUrl = 'http://192.168.110.131/tougao/public/'; -const tmrjournals = 'http://192.168.110.131/journal/public/'; -const baseUrl = '/api' +// const mediaUrl = 'https://submission.tmrjournals.com/public/'; +// // const mediaUrl = 'http://192.168.110.131/tougao/public/'; +// const tmrjournals = 'http://192.168.110.131/journal/public/'; +// const baseUrl = '/api' //测试环境 diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js index e390a86..db90620 100644 --- a/src/components/common/langs/en.js +++ b/src/components/common/langs/en.js @@ -1529,6 +1529,9 @@ const en = { tableBatchPosition: 'Batch Position', tableBatchPositionEmpty: 'No unpositioned tables', tableBatchPositionDone: 'Batch done: {ok} positioned, {skip} not found, {fail} failed', + tableDeleteAll: 'Delete all', + tableDeleteAllConfirm: 'Delete all tables?', + tableDeleteAllResult: 'Deleted {ok}, failed {fail}', removetable: 'Are you sure you want to delete this table?', reContent: 'Are you sure you want to restore this content?', uploadImageInfo: 'Figures can only upload files in JPG, JPEG, and PNG formats!', diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js index 0c210a9..2f1998b 100644 --- a/src/components/common/langs/zh.js +++ b/src/components/common/langs/zh.js @@ -1515,6 +1515,9 @@ const zh = { tableBatchPosition: '批量定位', tableBatchPositionEmpty: '没有待定位的表格', tableBatchPositionDone: '批量定位完成:成功 {ok},未匹配 {skip},失败 {fail}', + tableDeleteAll: '批量删除', + tableDeleteAllConfirm: '确定删除全部表格吗?', + tableDeleteAllResult: '已删除 {ok} 个,失败 {fail} 个', removetable: '确定要删除这个表格吗?', reContent: '确定要恢复这条内容吗?', uploadImageInfo: 'Figures 只能上传 JPG、JPEG 和 PNG 格式的文件', diff --git a/src/components/page/GenerateCharts.vue b/src/components/page/GenerateCharts.vue index aef55d0..702dad9 100644 --- a/src/components/page/GenerateCharts.vue +++ b/src/components/page/GenerateCharts.vue @@ -71,6 +71,7 @@ @edit="handleFigureAndTableEdit" @delete="handleFigureAndTableDelete" @deleteAllImages="handleDeleteAllFigures" + @deleteAllTables="handleDeleteAllTables" @autoPositionImage="handleAutoPositionImage" @autoPositionTable="handleAutoPositionTable" @batchAutoPositionImages="handleBatchAutoPositionImages" @@ -495,7 +496,7 @@ import bus from '@/components/common/bus'; import { del, isShallow } from 'vue'; import Tiff from 'tiff.js'; import { mediaUrl } from '@/common/js/commonJS.js'; // 引入通用逻辑 -import { LATEX_DATA_HTML_DATA, MATH_FORMULA_TABLE_TITLE } from '@/utils/mathFormulaModule'; +import { LATEX_DATA_HTML_DATA, MATH_FORMULA_TABLE_TITLE, isMathFormulaTableRecord } from '@/utils/mathFormulaModule'; import Tinymce from '@/components/page/components/Tinymce'; import bottomTinymce from '@/components/page/components/Tinymce'; import catalogue from '@/components/page/components/table/catalogue.vue'; @@ -1655,6 +1656,9 @@ export default { that.uploadWordTables = result; loading.close(); that.tablesHtmlVisible = true; + that.$nextTick(() => { + if (window.renderMathJax) window.renderMathJax(); + }); }) .catch((error) => { loading.close(); @@ -1966,6 +1970,82 @@ export default { this.$refs.commonWordHtmlTypeSetting.refresh('img'); } }, + async handleDeleteAllTables(list) { + const tables = Array.isArray(list) ? list.filter((item) => item && item.amt_id) : []; + if (!tables.length) { + return; + } + try { + await this.$confirm(this.$t('commonTable.tableDeleteAllConfirm'), 'Prompt', { + confirmButtonText: 'Submit', + cancelButtonText: 'Cancel', + type: 'warning' + }); + } catch (e) { + return; + } + const loading = this.$loading({ + lock: true, + text: 'Loading...', + spinner: 'el-icon-loading', + background: 'rgba(0, 0, 0, 0.7)' + }); + let ok = 0; + let fail = 0; + for (let i = 0; i < tables.length; i++) { + const item = tables[i]; + const replaceType = isMathFormulaTableRecord(item) ? 'math' : 'table'; + try { + // 已定位到正文:先解绑 api/Preaccept/removeTable,再删除 + const positionedRows = (this.Main_List || []).filter( + (row) => row && row.am_id && String(row.amt_id) === String(item.amt_id) + ); + let unboundOk = true; + for (let j = 0; j < positionedRows.length; j++) { + const row = positionedRows[j]; + const unbindRes = await this.$api.post(this.urlList.removePositioningTable, { + am_id: row.am_id + }); + if (unbindRes && unbindRes.code == 0) { + const idx = this.Main_List.findIndex((x) => x && x.am_id == row.am_id); + if (idx !== -1) { + this.Main_List.splice(idx, 1); + } + if (this.$refs.commonWordHtmlTypeSetting) { + this.$refs.commonWordHtmlTypeSetting.refresh('removeTable', { amt_id: item.amt_id }); + } + } else { + unboundOk = false; + break; + } + } + if (!unboundOk) { + fail += 1; + continue; + } + const res = await this.$api.post(this.urlList.deleteTable, { + amt_id: item.amt_id, + article_id: this.articleId + }); + if (res && res.status == 1) { + ok += 1; + if (this.$refs.commonWordHtmlTypeSetting) { + this.$refs.commonWordHtmlTypeSetting.replacement(replaceType, item.amt_id); + } + } else { + fail += 1; + } + } catch (err) { + fail += 1; + } + } + loading.close(); + this.$message.success(this.$t('commonTable.tableDeleteAllResult', { ok: ok, fail: fail })); + if (this.$refs.commonWordHtmlTypeSetting) { + this.$refs.commonWordHtmlTypeSetting.refresh('table'); + } + this.$forceUpdate(); + }, handleFigureAndTableEdit(data, type) { if (type == 'img') { var extension = data.url.split('.').pop().toLowerCase(); diff --git a/src/components/page/OnlineProofreading.vue b/src/components/page/OnlineProofreading.vue index 5a96784..b774180 100644 --- a/src/components/page/OnlineProofreading.vue +++ b/src/components/page/OnlineProofreading.vue @@ -81,6 +81,7 @@ @edit="handleImageEdit" @delete="handleFigureAndTableDelete" @deleteAllImages="handleDeleteAllFigures" + @deleteAllTables="handleDeleteAllTables" @autoPositionImage="handleAutoPositionImage" @autoPositionTable="handleAutoPositionTable" @batchAutoPositionImages="handleBatchAutoPositionImages" @@ -470,7 +471,7 @@ import bus from '@/components/common/bus'; import { del, isShallow } from 'vue'; import Tiff from 'tiff.js'; import { mediaUrl } from '@/common/js/commonJS.js'; // 引入通用逻辑 -import { LATEX_DATA_HTML_DATA, MATH_FORMULA_TABLE_TITLE } from '@/utils/mathFormulaModule'; +import { LATEX_DATA_HTML_DATA, MATH_FORMULA_TABLE_TITLE, isMathFormulaTableRecord } from '@/utils/mathFormulaModule'; import Tinymce from '@/components/page/components/Tinymce'; import bottomTinymce from '@/components/page/components/Tinymce'; import catalogue from '@/components/page/components/OnlineProofreading/catalogue.vue'; @@ -1374,6 +1375,9 @@ export default { that.uploadWordTables = result; loading.close(); that.tablesHtmlVisible = true; + that.$nextTick(() => { + if (window.renderMathJax) window.renderMathJax(); + }); }) .catch((error) => { loading.close(); @@ -1604,6 +1608,82 @@ export default { this.$refs.commonWordHtmlTypeSetting.refresh('img'); } }, + async handleDeleteAllTables(list) { + const tables = Array.isArray(list) ? list.filter((item) => item && item.amt_id) : []; + if (!tables.length) { + return; + } + try { + await this.$confirm(this.$t('commonTable.tableDeleteAllConfirm'), 'Prompt', { + confirmButtonText: 'Submit', + cancelButtonText: 'Cancel', + type: 'warning' + }); + } catch (e) { + return; + } + const loading = this.$loading({ + lock: true, + text: 'Loading...', + spinner: 'el-icon-loading', + background: 'rgba(0, 0, 0, 0.7)' + }); + let ok = 0; + let fail = 0; + for (let i = 0; i < tables.length; i++) { + const item = tables[i]; + const replaceType = isMathFormulaTableRecord(item) ? 'math' : 'table'; + try { + // 已定位到正文:先解绑 api/Preaccept/removeTable,再删除 + const positionedRows = (this.Main_List || []).filter( + (row) => row && row.am_id && String(row.amt_id) === String(item.amt_id) + ); + let unboundOk = true; + for (let j = 0; j < positionedRows.length; j++) { + const row = positionedRows[j]; + const unbindRes = await this.$api.post(this.urlList.removePositioningTable, { + am_id: row.am_id + }); + if (unbindRes && unbindRes.code == 0) { + const idx = this.Main_List.findIndex((x) => x && x.am_id == row.am_id); + if (idx !== -1) { + this.Main_List.splice(idx, 1); + } + if (this.$refs.commonWordHtmlTypeSetting) { + this.$refs.commonWordHtmlTypeSetting.refresh('removeTable', { amt_id: item.amt_id }); + } + } else { + unboundOk = false; + break; + } + } + if (!unboundOk) { + fail += 1; + continue; + } + const res = await this.$api.post(this.urlList.deleteTable, { + amt_id: item.amt_id, + article_id: this.articleId + }); + if (res && res.status == 1) { + ok += 1; + if (this.$refs.commonWordHtmlTypeSetting) { + this.$refs.commonWordHtmlTypeSetting.replacement(replaceType, item.amt_id); + } + } else { + fail += 1; + } + } catch (err) { + fail += 1; + } + } + loading.close(); + this.$message.success(this.$t('commonTable.tableDeleteAllResult', { ok: ok, fail: fail })); + if (this.$refs.commonWordHtmlTypeSetting) { + this.$refs.commonWordHtmlTypeSetting.refresh('table'); + } + this.$forceUpdate(); + }, handleTableAdd(type) { this.lineStyle = { note: '', table_data: '', html_data: '' }; this.lineStyle.visiTitle = 'Add Table'; diff --git a/src/components/page/components/table/wordHtmlTypesetting.vue b/src/components/page/components/table/wordHtmlTypesetting.vue index 1cdc9b7..8cdb982 100644 --- a/src/components/page/components/table/wordHtmlTypesetting.vue +++ b/src/components/page/components/table/wordHtmlTypesetting.vue @@ -347,8 +347,12 @@

--> -
+
{{ $t('commonTable.tableBatchPosition') }} + + {{ $t('commonTable.tableDeleteAll') }} +
@@ -843,6 +856,10 @@ export default { handleDeleteAllFigures() { this.$emit('deleteAllImages', [...(this.images || [])]); }, + handleDeleteAllTables() { + const list = (this.tables || []).filter((item) => item && item.amt_id); + this.$emit('deleteAllTables', list); + }, isMediaPositioned(item) { return !!(item && Number(item.has_selected) === 1); }, diff --git a/src/utils/wordMathImport.js b/src/utils/wordMathImport.js index 990c95c..fbc3afa 100644 --- a/src/utils/wordMathImport.js +++ b/src/utils/wordMathImport.js @@ -3567,6 +3567,10 @@ export async function importWordDocumentWithMath(file, options = {}) { if (options.forFigureExtract) { return html; } + // Tables 导入:保留表格结构,单元格内 OMML 已转 wmath,再扫一遍 $...$ + if (options.forTableExtract) { + return parseHtmlToLatex(html); + } if (options.keepReferences) { html = annotateOrderedListItemsInHtml(html); html = collapseEmptyParagraphsInHtml(