相关性代码调整
This commit is contained in:
1
scripts/h1-before.xml
Normal file
1
scripts/h1-before.xml
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="yes"?><w:document xmlns:wpc="http://schemas.microsoft.com/office/word/2010/wordprocessingCanvas" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships" xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math" xmlns:v="urn:schemas-microsoft-com:vml" xmlns:wp14="http://schemas.microsoft.com/office/word/2010/wordprocessingDrawing" xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing" xmlns:w10="urn:schemas-microsoft-com:office:word" xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main" xmlns:w14="http://schemas.microsoft.com/office/word/2010/wordml" xmlns:w15="http://schemas.microsoft.com/office/word/2012/wordml" xmlns:wpg="http://schemas.microsoft.com/office/word/2010/wordprocessingGroup" xmlns:wpi="http://schemas.microsoft.com/office/word/2010/wordprocessingInk" xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml" xmlns:wps="http://schemas.microsoft.com/office/word/2010/wordprocessingShape" xmlns:cx="http://schemas.microsoft.com/office/drawing/2014/chartex" xmlns:cx1="http://schemas.microsoft.com/office/drawing/2015/9/8/chartex" xmlns:cx2="http://schemas.microsoft.com/office/drawing/2015/10/21/chartex" xmlns:cx3="http://schemas.microsoft.com/office/drawing/2016/5/9/chartex" xmlns:cx4="http://schemas.microsoft.com/office/drawing/2016/5/10/chartex" xmlns:cx5="http://schemas.microsoft.com/office/drawing/2016/5/11/chartex" xmlns:cx6="http://schemas.microsoft.com/office/drawing/2016/5/12/chartex" xmlns:cx7="http://schemas.microsoft.com/office/drawing/2016/5/13/chartex" xmlns:cx8="http://schemas.microsoft.com/office/drawing/2016/5/14/chartex" xmlns:aink="http://schemas.microsoft.com/office/drawing/2016/ink" xmlns:am3d="http://schemas.microsoft.com/office/drawing/2017/model3d" xmlns:w16cex="http://schemas.microsoft.com/office/word/2018/wordml/cex" xmlns:w16cid="http://schemas.microsoft.com/office/word/2016/wordml/cid" xmlns:w16="http://schemas.microsoft.com/office/word/2018/wordml" xmlns:w16sdtdh="http://schemas.microsoft.com/office/word/2020/wordml/sdtdatahash" xmlns:w16se="http://schemas.microsoft.com/office/word/2015/wordml/symex" mc:Ignorable="w14 w15 wp14"><w:body><w:p><w:pPr><w:outlineLvl w:val="0"/></w:pPr><w:r><w:rPr><w:b/><w:bCs/><w:color w:val="D25A5A"/><w:sz w:val="18"/><w:szCs w:val="18"/></w:rPr><w:t xml:space="preserve">Introduction</w:t></w:r></w:p><w:sectPr><w:pgSz w:w="11906" w:h="16838" w:orient="portrait"/><w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="708" w:footer="708" w:gutter="0"/><w:pgNumType/><w:docGrid w:linePitch="360"/></w:sectPr></w:body></w:document>
|
||||||
BIN
scripts/test-h1-inject.docx
Normal file
BIN
scripts/test-h1-inject.docx
Normal file
Binary file not shown.
BIN
scripts/test-h1-out.docx
Normal file
BIN
scripts/test-h1-out.docx
Normal file
Binary file not shown.
0
scripts/test-h1-out.xml
Normal file
0
scripts/test-h1-out.xml
Normal file
BIN
scripts/test-h1-output.docx
Normal file
BIN
scripts/test-h1-output.docx
Normal file
Binary file not shown.
77
scripts/test-ref-html-export.mjs
Normal file
77
scripts/test-ref-html-export.mjs
Normal file
@@ -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('<!DOCTYPE html><html><body></body></html>');
|
||||||
|
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. <i>Journal One</i>. 2020;1:1-10. Available at:<br><span style="color:#0082AA">https://doi.org/10.1000/one</span>';
|
||||||
|
|
||||||
|
const bulkBr = '1. ' + sampleItem + '<br><br>2. ' + sampleItem.replace('Author A', 'Author B').replace('one', 'two');
|
||||||
|
|
||||||
|
const bulkDiv =
|
||||||
|
'<div>1. ' +
|
||||||
|
sampleItem +
|
||||||
|
'</div><div>2. ' +
|
||||||
|
sampleItem.replace('Author A', 'Author B').replace('one', 'two') +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
const bulkEditableDiv =
|
||||||
|
'<div>1. ' +
|
||||||
|
sampleItem +
|
||||||
|
'</div><div><br></div><div>2. ' +
|
||||||
|
sampleItem.replace('Author A', 'Author B').replace('one', 'two') +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
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: '<p>Intro text.</p>' }],
|
||||||
|
'',
|
||||||
|
[],
|
||||||
|
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');
|
||||||
@@ -19,8 +19,8 @@ const service = axios.create({
|
|||||||
// baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换
|
// baseURL: 'https://submission.tmrjournals.com/', //正式 记得切换
|
||||||
// baseURL: 'http://www.tougao.com/', //测试本地 记得切换
|
// baseURL: 'http://www.tougao.com/', //测试本地 记得切换
|
||||||
// baseURL: 'http://192.168.110.110/tougao/public/index.php/',
|
// baseURL: 'http://192.168.110.110/tougao/public/index.php/',
|
||||||
// baseURL: '/api', //本地
|
baseURL: '/api', //本地
|
||||||
baseURL: '/', //正式
|
// baseURL: '/', //正式
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import mammoth from "mammoth";
|
|||||||
import { MathfieldElement } from 'mathlive';
|
import { MathfieldElement } from 'mathlive';
|
||||||
import 'mathlive/dist/mathlive-static.css';
|
import 'mathlive/dist/mathlive-static.css';
|
||||||
import 'mathlive/dist/mathlive-fonts.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 api from '../../api/index.js';
|
||||||
import Common from '@/components/common/common'
|
import Common from '@/components/common/common'
|
||||||
import Tiff from 'tiff.js';
|
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")) {
|
if (type == 'table' && tag == 'img' && (attrName === "src" || attrName === "width" || attrName === "height")) {
|
||||||
return attrMatch;
|
return attrMatch;
|
||||||
}
|
}
|
||||||
|
if (tag === 'img' && (attrName === 'src' || attrName === 'width' || attrName === 'height' || attrName === 'alt')) {
|
||||||
|
return attrMatch;
|
||||||
|
}
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1527,7 +1530,7 @@ str = str.replace(regex, function (match, content, offset, fullString) {
|
|||||||
if (type == 'table') {
|
if (type == 'table') {
|
||||||
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|img|myfigure|mytable|myh3))[^>]+>/g, ''); // 删除不需要的标签
|
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|img|myfigure|mytable|myh3))[^>]+>/g, ''); // 删除不需要的标签
|
||||||
} else {
|
} 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) {
|
importWordDocumentWithMath(file, options) {
|
||||||
return parseWordDocumentWithMath(file);
|
return parseWordDocumentWithMath(file, options);
|
||||||
},
|
},
|
||||||
|
|
||||||
parseHtmlToLatex(html) {
|
parseHtmlToLatex(html) {
|
||||||
@@ -1552,42 +1555,35 @@ str = str.replace(regex, function (match, content, offset, fullString) {
|
|||||||
},
|
},
|
||||||
|
|
||||||
cleanAndParseWordContent(content) {
|
cleanAndParseWordContent(content) {
|
||||||
// 1️⃣ 解析成 <p> 段落数组
|
const rows = parseImportedHtmlToContentRows(content);
|
||||||
let tempDiv = document.createElement('div');
|
const parsedData = [];
|
||||||
tempDiv.innerHTML = content; // 解析 HTML 内容
|
|
||||||
let paragraphs = tempDiv.querySelectorAll("p"); // 选取所有 <p> 作为数据项
|
|
||||||
|
|
||||||
// 2️⃣ 将 <p> 内容转换为数组,并处理内容
|
rows.forEach((raw) => {
|
||||||
let parsedData = Array.from(paragraphs).map(p => {
|
const rawStr = String(raw || '');
|
||||||
let text = p.innerHTML.trim(); // 获取内容,去除两端空格
|
if (rawStr && (/wordTableHtml/i.test(rawStr) || /<table[\s>]/i.test(rawStr))) {
|
||||||
text = replaceNegativeSign(text);
|
parsedData.push(/wordTableHtml/i.test(rawStr) ? rawStr : `<div class="thumbnailTableBox wordTableHtml table_Box">${rawStr}</div>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rawStr === '') {
|
||||||
|
parsedData.push('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
text = this.transformHtmlString(text)
|
let text = replaceNegativeSign(rawStr.trim());
|
||||||
// 3️⃣ **正确移除 <o:p>(Word 复制的无效标签)**
|
text = this.transformHtmlString(text);
|
||||||
text = text.replace(/<\/?o:p[^>]*>/g, "");
|
text = text.replace(/<\/?o:p[^>]*>/g, '');
|
||||||
|
text = text.replace(/\s*style="[^"]*"/gi, '');
|
||||||
// 4️⃣ **移除所有 style="..."**
|
text = text.replace(/<strong>/gi, '<b>').replace(/<\/strong>/gi, '</b>');
|
||||||
text = text.replace(/\s*style="[^"]*"/gi, "");
|
text = text.replace(/<em>/gi, '<i>').replace(/<\/em>/gi, '</i>');
|
||||||
|
text = text.replace(/<span>\s*<\/span>/gi, '');
|
||||||
// 5️⃣ **修正标签替换**
|
text = text.replace(/<b>\s*<\/b>/gi, '');
|
||||||
text = text.replace(/<strong>/gi, "<b>").replace(/<\/strong>/gi, "</b>");
|
text = text.replace(/<i>\s*<\/i>/gi, '');
|
||||||
text = text.replace(/<em>/gi, "<i>").replace(/<\/em>/gi, "</i>");
|
text = text.replace(/<[^\/>]+>\s*<\/[^>]+>/gi, (match) => (match.trim() === '' ? '' : match));
|
||||||
|
text = mergeAdjacentBlueTags(text);
|
||||||
// 6️⃣ **移除空的 span、b、i 标签**
|
text = normalizeSpacesAroundBlueTags(text);
|
||||||
text = text.replace(/<span>\s*<\/span>/gi, "");
|
parsedData.push(text.trim() === '' ? '' : text);
|
||||||
text = text.replace(/<b>\s*<\/b>/gi, "");
|
|
||||||
text = text.replace(/<i>\s*<\/i>/gi, "");
|
|
||||||
|
|
||||||
// 7️⃣ **确保不移除半个标签(修复匹配规则)**
|
|
||||||
text = text.replace(/<[^\/>]+>\s*<\/[^>]+>/gi, match => {
|
|
||||||
return match.trim() === "" ? "" : match;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 8️⃣ **返回最终内容**
|
|
||||||
return text.trim() === "" ? "" : text;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
return parsedData;
|
return parsedData;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -2865,12 +2861,12 @@ str = str.replace(regex, function (match, content, offset, fullString) {
|
|||||||
background: 'rgba(0, 0, 0, 0.45)'
|
background: 'rgba(0, 0, 0, 0.45)'
|
||||||
})
|
})
|
||||||
: null;
|
: null;
|
||||||
parseWordDocumentWithMath(file)
|
parseWordDocumentWithMath(file, { textOnly: true })
|
||||||
.then((html) => {
|
.then((html) => {
|
||||||
if (!html) {
|
if (!html) {
|
||||||
throw new Error('empty content');
|
throw new Error('empty content');
|
||||||
}
|
}
|
||||||
ed.insertContent(html);
|
ed.setContent(html);
|
||||||
const body = ed.getBody();
|
const body = ed.getBody();
|
||||||
body.querySelectorAll('wmath').forEach((el) => {
|
body.querySelectorAll('wmath').forEach((el) => {
|
||||||
let latex = (el.getAttribute('data-latex') || '').trim();
|
let latex = (el.getAttribute('data-latex') || '').trim();
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
//记得切换
|
//记得切换
|
||||||
|
|
||||||
//正式
|
//正式
|
||||||
const mediaUrl = '/public/';
|
// const mediaUrl = '/public/';
|
||||||
const baseUrl = '/';
|
// const baseUrl = '/';
|
||||||
|
|
||||||
//正式环境
|
//正式环境
|
||||||
|
|
||||||
// const mediaUrl = 'https://submission.tmrjournals.com/public/';
|
const mediaUrl = 'https://submission.tmrjournals.com/public/';
|
||||||
// // const mediaUrl = 'http://zmzm.tougao.dev.com/public/';
|
// const mediaUrl = 'http://zmzm.tougao.dev.com/public/';
|
||||||
// const baseUrl = '/api'
|
const baseUrl = '/api'
|
||||||
|
|
||||||
//测试环境
|
//测试环境
|
||||||
|
|
||||||
|
|||||||
@@ -382,7 +382,9 @@ const en = {
|
|||||||
unsubscribeSwitchOff: 'Subscribed',
|
unsubscribeSwitchOff: 'Subscribed',
|
||||||
unsubscribeMissingId: 'Missing expert ID, unable to switch unsubscribe status',
|
unsubscribeMissingId: 'Missing expert ID, unable to switch unsubscribe status',
|
||||||
unsubscribeUpdateSuccess: 'Unsubscribe status updated',
|
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: {
|
countryManagement: {
|
||||||
title: 'Country Management',
|
title: 'Country Management',
|
||||||
@@ -1146,10 +1148,12 @@ const en = {
|
|||||||
AnnotationList: 'Annotation List',
|
AnnotationList: 'Annotation List',
|
||||||
Annotations: 'Comments',
|
Annotations: 'Comments',
|
||||||
exportWord: 'Generate Word file',
|
exportWord: 'Generate Word file',
|
||||||
|
exportWordShort: 'Word',
|
||||||
exportManuscriptEmpty: 'No content to export.',
|
exportManuscriptEmpty: 'No content to export.',
|
||||||
exportManuscriptSuccess: 'Word downloaded.',
|
exportManuscriptSuccess: 'Word downloaded.',
|
||||||
exportManuscriptFail: 'Failed to export Word.',
|
exportManuscriptFail: 'Failed to export Word.',
|
||||||
citeRelevanceDetect: 'Download relevance HTML',
|
citeRelevanceDetect: 'Download relevance HTML',
|
||||||
|
citeRelevanceDetectShort: 'Relevance',
|
||||||
citeRelevanceTitle: 'Citation relevance check',
|
citeRelevanceTitle: 'Citation relevance check',
|
||||||
citeRelevanceTitleProgress: 'Citation relevance ({current}/{total})',
|
citeRelevanceTitleProgress: 'Citation relevance ({current}/{total})',
|
||||||
citeRelevanceEmpty: 'No citations to review.',
|
citeRelevanceEmpty: 'No citations to review.',
|
||||||
@@ -1182,8 +1186,34 @@ const en = {
|
|||||||
citeRelevanceCopyGroup: 'Copy paragraph',
|
citeRelevanceCopyGroup: 'Copy paragraph',
|
||||||
citeRelevanceDownloadHtml: 'Download HTML',
|
citeRelevanceDownloadHtml: 'Download HTML',
|
||||||
citeRelevanceDownloadHtmlSuccess: 'HTML downloaded',
|
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',
|
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 <i> 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',
|
refHtmlToWord: 'References HTML to Word',
|
||||||
refHtmlToWordSuccess: 'Word exported with edited references',
|
refHtmlToWordSuccess: 'Word exported with edited references',
|
||||||
refHtmlToWordFail: 'Failed to convert references HTML to Word',
|
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.',
|
refHtmlParseEmpty: 'No reference entries found in HTML. Please use the HTML downloaded from this system.',
|
||||||
refHtmlPageTitle: 'References editor',
|
refHtmlPageTitle: 'References editor',
|
||||||
refHtmlReferencesTitle: 'References',
|
refHtmlReferencesTitle: 'References',
|
||||||
refHtmlIntro: 'Edit each reference directly, or paste AI output (supports <i> italics and other HTML). When done, click "Download edited HTML", then use "References HTML to Word" on the typesetting page.',
|
refHtmlIntro: 'Download to edit all references in one block, or paste AI output (supports <i> 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, then import it 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',
|
refHtmlDownloadEdited: 'Download edited HTML',
|
||||||
refHtmlDownloadFileName: 'references-edited.html',
|
refHtmlDownloadFileName: 'references-edited.html',
|
||||||
exportImg: 'Export PNG',
|
exportImg: 'Export PNG',
|
||||||
@@ -1264,6 +1294,8 @@ const en = {
|
|||||||
Row: "Row",
|
Row: "Row",
|
||||||
addRow: "Add Row",
|
addRow: "Add Row",
|
||||||
Uncheck: 'Uncheck the paragraph',
|
Uncheck: 'Uncheck the paragraph',
|
||||||
|
selectAll: 'Select All',
|
||||||
|
selectAllShort: 'All',
|
||||||
ManuscirptAIProofreading: 'Manuscript AI Proofreading',
|
ManuscirptAIProofreading: 'Manuscript AI Proofreading',
|
||||||
AIProofreading: 'AI Proofreading',
|
AIProofreading: 'AI Proofreading',
|
||||||
Association: 'Association',
|
Association: 'Association',
|
||||||
|
|||||||
@@ -371,7 +371,9 @@ const zh = {
|
|||||||
unsubscribeSwitchOff: '已订阅',
|
unsubscribeSwitchOff: '已订阅',
|
||||||
unsubscribeMissingId: '缺少专家 ID,无法切换退订状态',
|
unsubscribeMissingId: '缺少专家 ID,无法切换退订状态',
|
||||||
unsubscribeUpdateSuccess: '退订状态更新成功',
|
unsubscribeUpdateSuccess: '退订状态更新成功',
|
||||||
unsubscribeUpdateFailed: '退订状态更新失败'
|
unsubscribeUpdateFailed: '退订状态更新失败',
|
||||||
|
lastContactTime: '最近一次联系时间:{time}',
|
||||||
|
lastContactTimeEmpty: '暂无联系记录'
|
||||||
},
|
},
|
||||||
countryManagement: {
|
countryManagement: {
|
||||||
title: '国家信息维护',
|
title: '国家信息维护',
|
||||||
@@ -1132,10 +1134,12 @@ const zh = {
|
|||||||
AnnotationList: '批注列表',
|
AnnotationList: '批注列表',
|
||||||
Annotations: '批注',
|
Annotations: '批注',
|
||||||
exportWord: '生成 Word 文件',
|
exportWord: '生成 Word 文件',
|
||||||
|
exportWordShort: 'Word',
|
||||||
exportManuscriptEmpty: '没有可导出的内容。',
|
exportManuscriptEmpty: '没有可导出的内容。',
|
||||||
exportManuscriptSuccess: 'Word 已下载。',
|
exportManuscriptSuccess: 'Word 已下载。',
|
||||||
exportManuscriptFail: 'Word 导出失败。',
|
exportManuscriptFail: 'Word 导出失败。',
|
||||||
citeRelevanceDetect: '下载相关性 HTML',
|
citeRelevanceDetect: '下载相关性 HTML',
|
||||||
|
citeRelevanceDetectShort: '相关性',
|
||||||
citeRelevanceTitle: '引文相关性核查',
|
citeRelevanceTitle: '引文相关性核查',
|
||||||
citeRelevanceTitleProgress: '引文相关性核查 ({current}/{total})',
|
citeRelevanceTitleProgress: '引文相关性核查 ({current}/{total})',
|
||||||
citeRelevanceEmpty: '当前没有可核查的引文。',
|
citeRelevanceEmpty: '当前没有可核查的引文。',
|
||||||
@@ -1168,8 +1172,34 @@ const zh = {
|
|||||||
citeRelevanceCopyGroup: '复制本段',
|
citeRelevanceCopyGroup: '复制本段',
|
||||||
citeRelevanceDownloadHtml: '下载 HTML',
|
citeRelevanceDownloadHtml: '下载 HTML',
|
||||||
citeRelevanceDownloadHtmlSuccess: 'HTML 已下载',
|
citeRelevanceDownloadHtmlSuccess: 'HTML 已下载',
|
||||||
|
refHtmlCopy: '复制参考文献',
|
||||||
|
refHtmlCopyShort: '复制',
|
||||||
|
refHtmlCopySuccess: '参考文献已复制(含编号、期刊斜体、蓝色 DOI、Available at 换行)',
|
||||||
|
parseBookReference: '解析书籍',
|
||||||
|
parseBookReferenceSuccess: '书籍字段已按规则解析',
|
||||||
|
parseBookReferenceEmpty: '无法从 Content 中解析书籍信息,请检查格式(Author. Title. Publication Details. ISBN)',
|
||||||
|
parseBookReferenceContentEmpty: 'Content 不能为空',
|
||||||
refHtmlDownload: '下载参考文献 HTML',
|
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. 编号分隔,或条目之间空一行;也支持 <i> 斜体等 HTML 标签。',
|
||||||
|
refHtmlPastePlaceholder: '在此粘贴完整参考文献内容…',
|
||||||
|
refHtmlPasteParsed: '已识别 {n} 条参考文献',
|
||||||
|
refHtmlPasteParseEmpty: '暂未识别到参考文献,请使用「复制参考文献」的格式(1. 2. 编号 + Available at 换行)',
|
||||||
|
refHtmlPasteExport: '导出 Word',
|
||||||
|
refHtmlPasteCancel: '取消',
|
||||||
|
refHtmlImportHtml: '从 HTML 文件导入',
|
||||||
|
refHtmlImportSuccess: '已导入到编辑框,确认后点击「导出 Word」',
|
||||||
|
refHtmlBulkHint: '以下为整段参考文献,可整体替换或粘贴 AI 返回的完整内容;条目之间用空行或 1. 2. 编号分隔。',
|
||||||
refHtmlToWord: '参考文献 HTML 转 Word',
|
refHtmlToWord: '参考文献 HTML 转 Word',
|
||||||
refHtmlToWordSuccess: 'Word 已导出(含编辑后的参考文献)',
|
refHtmlToWordSuccess: 'Word 已导出(含编辑后的参考文献)',
|
||||||
refHtmlToWordFail: '参考文献 HTML 转 Word 失败',
|
refHtmlToWordFail: '参考文献 HTML 转 Word 失败',
|
||||||
@@ -1178,8 +1208,8 @@ const zh = {
|
|||||||
refHtmlParseEmpty: 'HTML 中未找到参考文献条目,请使用本系统下载的参考文献 HTML',
|
refHtmlParseEmpty: 'HTML 中未找到参考文献条目,请使用本系统下载的参考文献 HTML',
|
||||||
refHtmlPageTitle: '参考文献编辑',
|
refHtmlPageTitle: '参考文献编辑',
|
||||||
refHtmlReferencesTitle: 'References',
|
refHtmlReferencesTitle: 'References',
|
||||||
refHtmlIntro: '可直接编辑每条文献,或粘贴 AI 返回的内容(支持 <i> 斜体等 HTML 标签)。编辑完成后点击「下载编辑后的 HTML」,再在排版页使用「参考文献 HTML 转 Word」。',
|
refHtmlIntro: '下载后可在一个大文本框里整体编辑全部参考文献,或粘贴 AI 返回的完整内容(支持 <i> 斜体等 HTML 标签)。编辑完成后保存 HTML,或在排版页使用「粘贴参考文献」导出 Word。',
|
||||||
refHtmlSaveTip: '提示:编辑后请点击上方按钮保存 HTML;也可在排版页导入该文件导出 Word。',
|
refHtmlSaveTip: '提示:整段编辑后点击上方按钮保存 HTML;也可复制内容后在排版页「粘贴参考文献」导出 Word。',
|
||||||
refHtmlDownloadEdited: '下载编辑后的 HTML',
|
refHtmlDownloadEdited: '下载编辑后的 HTML',
|
||||||
refHtmlDownloadFileName: 'references-edited.html',
|
refHtmlDownloadFileName: 'references-edited.html',
|
||||||
exportImg: '导出 图片',
|
exportImg: '导出 图片',
|
||||||
@@ -1249,6 +1279,8 @@ const zh = {
|
|||||||
Row:"空行",
|
Row:"空行",
|
||||||
addRow:"新增空行",
|
addRow:"新增空行",
|
||||||
Uncheck:'取消勾选段落',
|
Uncheck:'取消勾选段落',
|
||||||
|
selectAll: '全选',
|
||||||
|
selectAllShort: '全选',
|
||||||
ManuscirptAIProofreading:'稿件AI校对',
|
ManuscirptAIProofreading:'稿件AI校对',
|
||||||
AIProofreading:'AI校对',
|
AIProofreading:'AI校对',
|
||||||
Association:'关联',
|
Association:'关联',
|
||||||
|
|||||||
@@ -416,7 +416,8 @@
|
|||||||
type="content"
|
type="content"
|
||||||
@openLatexEditor="openLatexEditor"
|
@openLatexEditor="openLatexEditor"
|
||||||
v-if="addContentVisible"
|
v-if="addContentVisible"
|
||||||
:enable-import-word-math="true"
|
:enable-import-word-math="zyModeEnabled"
|
||||||
|
:article-id="articleId"
|
||||||
ref="addContent"
|
ref="addContent"
|
||||||
style="margin-left: -115px"
|
style="margin-left: -115px"
|
||||||
></common-content>
|
></common-content>
|
||||||
@@ -445,6 +446,7 @@ import { LATEX_DATA_HTML_DATA, MATH_FORMULA_TABLE_TITLE } from '@/utils/mathForm
|
|||||||
import Tinymce from '@/components/page/components/Tinymce';
|
import Tinymce from '@/components/page/components/Tinymce';
|
||||||
import bottomTinymce from '@/components/page/components/Tinymce';
|
import bottomTinymce from '@/components/page/components/Tinymce';
|
||||||
import catalogue from '@/components/page/components/table/catalogue.vue';
|
import catalogue from '@/components/page/components/table/catalogue.vue';
|
||||||
|
import { isZyModeEnabled } from '@/utils/zyMode';
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -578,6 +580,9 @@ export default {
|
|||||||
catalogue
|
catalogue
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
zyModeEnabled() {
|
||||||
|
return isZyModeEnabled(this.$route);
|
||||||
|
},
|
||||||
combinedValue() {
|
combinedValue() {
|
||||||
// 将两个值组合成一个新的值,可以是字符串、数组、对象等
|
// 将两个值组合成一个新的值,可以是字符串、数组、对象等
|
||||||
// return `${this.isFirstComponentLoaded}-${this.isWordComponentLoaded}`;
|
// return `${this.isFirstComponentLoaded}-${this.isWordComponentLoaded}`;
|
||||||
@@ -726,17 +731,7 @@ export default {
|
|||||||
|
|
||||||
this.saveContent(content, this.currentContent.am_id);
|
this.saveContent(content, this.currentContent.am_id);
|
||||||
} else if (type == 'addcontent') {
|
} else if (type == 'addcontent') {
|
||||||
var hasTable = /<table[\s\S]*?>[\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);
|
var list = this.$commonJS.cleanAndParseWordContent(content);
|
||||||
|
|
||||||
|
|
||||||
this.saveContentList(list, this.currentId);
|
this.saveContentList(list, this.currentId);
|
||||||
} else if (type == 'table') {
|
} else if (type == 'table') {
|
||||||
|
|||||||
@@ -726,17 +726,7 @@ export default {
|
|||||||
|
|
||||||
this.saveContent(content, this.currentContent.am_id);
|
this.saveContent(content, this.currentContent.am_id);
|
||||||
} else if (type == 'addcontent') {
|
} else if (type == 'addcontent') {
|
||||||
var hasTable = /<table[\s\S]*?>[\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);
|
var list = this.$commonJS.cleanAndParseWordContent(content);
|
||||||
|
|
||||||
|
|
||||||
this.saveContentList(list, this.currentId);
|
this.saveContentList(list, this.currentId);
|
||||||
} else if (type == 'table') {
|
} else if (type == 'table') {
|
||||||
|
|||||||
@@ -1182,7 +1182,7 @@ import {
|
|||||||
getAuthorDisplayName,
|
getAuthorDisplayName,
|
||||||
getAuthorAffiliationPreview
|
getAuthorAffiliationPreview
|
||||||
} from '@/utils/productionSubmissionImport';
|
} from '@/utils/productionSubmissionImport';
|
||||||
import { isZyModeEnabled } from '@/utils/zyMode';
|
import { isZyModeEnabled, isZySkipCheckEnabled } from '@/utils/zyMode';
|
||||||
export default {
|
export default {
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
@@ -2892,39 +2892,19 @@ export default {
|
|||||||
|
|
||||||
// 6----创建文章
|
// 6----创建文章
|
||||||
EstaBlish() {
|
EstaBlish() {
|
||||||
|
if (isZySkipCheckEnabled(this.$route)) {
|
||||||
|
this.doTypeSettingNewDirect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
this.$api
|
this.$api
|
||||||
.post('api/Production/checkRefer', {
|
.post('api/Production/checkRefer', {
|
||||||
p_article_id: this.p_article_id
|
p_article_id: this.p_article_id
|
||||||
})
|
})
|
||||||
.then((res) => {
|
.then((res) => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
const loading = this.$loading({
|
this.doTypeSettingNewDirect();
|
||||||
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();
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
this.$message.error(res.msg);
|
this.$message.error(res.msg);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((err) => {
|
.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----校对文章
|
// 6----校对文章
|
||||||
htmlContet() {
|
htmlContet() {
|
||||||
this.$api
|
this.$api
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { Document, Packer, PageOrientation, Paragraph, TextRun } from 'docx'; //
|
|||||||
import html2canvas from 'html2canvas';
|
import html2canvas from 'html2canvas';
|
||||||
import { extractHexImagesFromRTF, hexToBlob } from '@/utils/rtfParser';
|
import { extractHexImagesFromRTF, hexToBlob } from '@/utils/rtfParser';
|
||||||
import { tableStyle } from '@/utils/tinymceStyles';
|
import { tableStyle } from '@/utils/tinymceStyles';
|
||||||
|
import { normalizeEditorPasteHtml, isStructuredDataTable, normalizeSpacesAroundBlueTags } from '@/utils/wordMathImport';
|
||||||
export default {
|
export default {
|
||||||
name: 'tinymce',
|
name: 'tinymce',
|
||||||
components: {},
|
components: {},
|
||||||
@@ -63,6 +64,11 @@ export default {
|
|||||||
},
|
},
|
||||||
articleId: {
|
articleId: {
|
||||||
default: ''
|
default: ''
|
||||||
|
},
|
||||||
|
/** Word 批量导入:保留 base64 图片,不自动上传 */
|
||||||
|
keepInlineImages: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
@@ -698,6 +704,8 @@ export default {
|
|||||||
plugins: 'texttransform noneditable table image', // 启用 forecolor 和 code 插件
|
plugins: 'texttransform noneditable table image', // 启用 forecolor 和 code 插件
|
||||||
// plugins: 'forecolor code paste table image mathType searchreplace raw', // 启用 forecolor 和 code 插件
|
// plugins: 'forecolor code paste table image mathType searchreplace raw', // 启用 forecolor 和 code 插件
|
||||||
end_container_on_empty_block: true,
|
end_container_on_empty_block: true,
|
||||||
|
automatic_uploads: !_this.keepInlineImages,
|
||||||
|
paste_data_images: true,
|
||||||
content_css: 'default', // 加载 TinyMCE 默认样式表
|
content_css: 'default', // 加载 TinyMCE 默认样式表
|
||||||
mathjax: {
|
mathjax: {
|
||||||
// 配置 MathJax 用于渲染数学公式
|
// 配置 MathJax 用于渲染数学公式
|
||||||
@@ -707,6 +715,19 @@ export default {
|
|||||||
// automatic_uploads: false,
|
// automatic_uploads: false,
|
||||||
images_upload_handler: function (blobInfo, progress) {
|
images_upload_handler: function (blobInfo, progress) {
|
||||||
return new Promise((resolve, reject) => {
|
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 xhr = new XMLHttpRequest();
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
const file = blobInfo.blob();
|
const file = blobInfo.blob();
|
||||||
@@ -926,23 +947,28 @@ export default {
|
|||||||
e.content = e.content.replace(/<i>/g, '<em>').replace(/<\/i>/g, '</em>');
|
e.content = e.content.replace(/<i>/g, '<em>').replace(/<\/i>/g, '</em>');
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
paste_preprocess: function (plugin, args) {
|
paste_preprocess: function (editor, args) {
|
||||||
let imgIdx = 0;
|
let imgIdx = 0;
|
||||||
|
|
||||||
const silentPlaceholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
const silentPlaceholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||||
|
|
||||||
let content = args.content.replace(/(<img[^>]*?)src="file:\/\/\/[^" ]*"/gi, (match, p1) => {
|
let content = String(args.content || '').replace(/(<img[^>]*?)src="file:\/\/\/[^" ]*"/gi, (match, p1) => {
|
||||||
// 保留 data-idx, Word 里的尺寸
|
|
||||||
return `${p1}src="${silentPlaceholder}" class="word-img-placeholder" data-idx="${imgIdx++}"`;
|
return `${p1}src="${silentPlaceholder}" class="word-img-placeholder" data-idx="${imgIdx++}"`;
|
||||||
});
|
});
|
||||||
let tempDiv = document.createElement('div');
|
let tempDiv = document.createElement('div');
|
||||||
tempDiv.innerHTML = content;
|
tempDiv.innerHTML = content;
|
||||||
|
|
||||||
if (tempDiv.querySelector('table')) {
|
const tableEl = tempDiv.querySelector('table');
|
||||||
if (_this.type == 'table') {
|
const isRealDataTable =
|
||||||
|
tableEl &&
|
||||||
_this.$commonJS.parseTableToArray(content, (tableList) => {
|
_this.type === 'table' &&
|
||||||
var contentHtml = `
|
_this.isAutomaticUpdate &&
|
||||||
|
isStructuredDataTable(tableEl);
|
||||||
|
|
||||||
|
if (isRealDataTable) {
|
||||||
|
_this.$commonJS.parseTableToArray(content, (tableList) => {
|
||||||
|
if (!tableList || !tableList.length) return;
|
||||||
|
const contentHtml = `
|
||||||
<div class="thumbnailTableBox wordTableHtml table_Box table_Box3333" style="">
|
<div class="thumbnailTableBox wordTableHtml table_Box table_Box3333" style="">
|
||||||
<table border="1" style="width: auto; border-collapse: collapse; text-align: center;">
|
<table border="1" style="width: auto; border-collapse: collapse; text-align: center;">
|
||||||
${tableList
|
${tableList
|
||||||
@@ -965,12 +991,10 @@ export default {
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
const container = document.createElement('div');
|
||||||
const container = document.createElement('div');
|
container.innerHTML = contentHtml;
|
||||||
container.innerHTML = contentHtml;
|
args.content = normalizeEditorPasteHtml(container.innerHTML);
|
||||||
args.content = container.innerHTML; // 更新处理后的内容
|
});
|
||||||
});
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
const plainText = (tempDiv.textContent || tempDiv.innerText || '').trim();
|
const plainText = (tempDiv.textContent || tempDiv.innerText || '').trim();
|
||||||
const builtPlain = _this.buildWmathHtmlFromLatexText(plainText);
|
const builtPlain = _this.buildWmathHtmlFromLatexText(plainText);
|
||||||
@@ -987,18 +1011,18 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.event) {
|
if (!isRealDataTable) {
|
||||||
args.event.preventDefault();
|
const normalized = normalizeEditorPasteHtml(content);
|
||||||
args.event.stopPropagation();
|
if (_this.isAutomaticUpdate) {
|
||||||
}
|
args.content = _this.$commonJS.transformHtmlString(normalized);
|
||||||
|
} else {
|
||||||
if (_this.isAutomaticUpdate) {
|
args.content = normalized;
|
||||||
args.content = _this.$commonJS.transformHtmlString(content); // 更新处理后的内容
|
}
|
||||||
} else {
|
|
||||||
args.content = content;
|
|
||||||
}
|
}
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
window.renderMathJax(_this.tinymceId);
|
if (typeof window.renderMathJax === 'function') {
|
||||||
|
window.renderMathJax(_this.tinymceId);
|
||||||
|
}
|
||||||
}, 10);
|
}, 10);
|
||||||
},
|
},
|
||||||
clear_custom_action: (editor, vm) => {
|
clear_custom_action: (editor, vm) => {
|
||||||
@@ -1101,7 +1125,11 @@ export default {
|
|||||||
content = content.replace(/<span[^>]*>/g, '').replace(/<\/span>/g, ''); // 去除span标签
|
content = content.replace(/<span[^>]*>/g, '').replace(/<\/span>/g, ''); // 去除span标签
|
||||||
content = content.replace(/<strong>/g, '<b>').replace(/<\/strong>/g, '</b>');
|
content = content.replace(/<strong>/g, '<b>').replace(/<\/strong>/g, '</b>');
|
||||||
content = content.replace(/<em>/g, '<i>').replace(/<\/em>/g, '</i>');
|
content = content.replace(/<em>/g, '<i>').replace(/<\/em>/g, '</i>');
|
||||||
content = content.replace(/ /g, ' '); // 将所有 替换为空格
|
if (type === 'addcontent') {
|
||||||
|
content = normalizeSpacesAroundBlueTags(content);
|
||||||
|
} else {
|
||||||
|
content = content.replace(/ /g, ' ');
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('getContent', type, content);
|
this.$emit('getContent', type, content);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
ref="tinymceChild1"
|
ref="tinymceChild1"
|
||||||
:wordStyle="wordStyle"
|
:wordStyle="wordStyle"
|
||||||
:isAutomaticUpdate="isAutomaticUpdate"
|
:isAutomaticUpdate="isAutomaticUpdate"
|
||||||
|
:article-id="articleId"
|
||||||
|
:keep-inline-images="enableImportWordMath"
|
||||||
@getContent="getContent"
|
@getContent="getContent"
|
||||||
@openLatexEditor="openLatexEditor"
|
@openLatexEditor="openLatexEditor"
|
||||||
@updateChange="updateChange"
|
@updateChange="updateChange"
|
||||||
@@ -48,6 +50,9 @@ export default {
|
|||||||
enableImportWordMath: {
|
enableImportWordMath: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
default: false
|
default: false
|
||||||
|
},
|
||||||
|
articleId: {
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
components: {
|
components: {
|
||||||
@@ -62,9 +67,9 @@ export default {
|
|||||||
if (!this.isAutomaticUpdate) {
|
if (!this.isAutomaticUpdate) {
|
||||||
groups.push('LateX');
|
groups.push('LateX');
|
||||||
}
|
}
|
||||||
// if (this.enableImportWordMath) {
|
if (this.enableImportWordMath) {
|
||||||
// groups.push('importWordMath');
|
groups.push('importWordMath');
|
||||||
// }
|
}
|
||||||
groups.push(
|
groups.push(
|
||||||
'myuppercase myuppercasea Line MoreSymbols',
|
'myuppercase myuppercasea Line MoreSymbols',
|
||||||
'subscript superscript',
|
'subscript superscript',
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
:style="{ '--p-l': '10px', '--p-l': '10px', '--fl-ai': 'center' }"
|
:style="{ '--p-l': '10px', '--p-l': '10px', '--fl-ai': 'center' }"
|
||||||
>
|
>
|
||||||
<el-button @click="handleUncheck" v-if="!isPreview" plain type="info">{{ $t('commonTable.Uncheck') }}</el-button>
|
<el-button @click="handleUncheck" v-if="!isPreview" plain type="info">{{ $t('commonTable.Uncheck') }}</el-button>
|
||||||
|
<el-button @click="handleSelectAll" v-if="!isPreview && zyModeEnabled" plain type="info" size="mini">
|
||||||
|
<i class="el-icon-circle-check"></i> {{ $t('commonTable.selectAllShort') }}
|
||||||
|
</el-button>
|
||||||
<div
|
<div
|
||||||
@click="handleClickAI()"
|
@click="handleClickAI()"
|
||||||
v-if="isEditComment && !isPreview && activeName == 'proofreading'"
|
v-if="isEditComment && !isPreview && activeName == 'proofreading'"
|
||||||
@@ -76,39 +79,78 @@
|
|||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="zyModeEnabled"
|
v-if="zyModeEnabled"
|
||||||
@click="handleExportManuscriptWord"
|
@click="handleDownloadReferencesHtml"
|
||||||
class="base-font-size base-bg-imp base-padding-all"
|
class="zy-toolbar-btn"
|
||||||
:style="{ '--f-s': '12px', '--f-c': '#333' }"
|
|
||||||
>
|
>
|
||||||
<i
|
<el-tooltip :content="$t('commonTable.refHtmlDownload')" placement="bottom">
|
||||||
class="el-icon-download base-margin"
|
<span class="zy-toolbar-btn-inner">
|
||||||
:style="{ '--m-r': '2px' }"
|
<i
|
||||||
v-if="!exportingManuscriptWord"
|
class="el-icon-document"
|
||||||
></i>
|
v-if="!referencesHtmlLoading"
|
||||||
<i
|
></i>
|
||||||
class="el-icon-loading base-margin"
|
<i class="el-icon-loading" v-else></i>
|
||||||
:style="{ '--m-r': '2px' }"
|
<span class="zy-toolbar-label">{{ $t('commonTable.refHtmlDownloadShort') }}</span>
|
||||||
v-else
|
</span>
|
||||||
></i>
|
</el-tooltip>
|
||||||
{{ $t('commonTable.exportWord') }}
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="zyModeEnabled"
|
||||||
|
@click="triggerReferencesUpload"
|
||||||
|
@dblclick.stop="openReferencesPasteDialog"
|
||||||
|
class="zy-toolbar-btn"
|
||||||
|
>
|
||||||
|
<el-tooltip
|
||||||
|
:content="uploadedReferencesCount ? $t('commonTable.refHtmlUploadLoadedTip', { n: uploadedReferencesCount }) : $t('commonTable.refHtmlUpload')"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<span class="zy-toolbar-btn-inner">
|
||||||
|
<i
|
||||||
|
class="el-icon-upload2"
|
||||||
|
v-if="!referencesUploadLoading"
|
||||||
|
></i>
|
||||||
|
<i class="el-icon-loading" v-else></i>
|
||||||
|
<span class="zy-toolbar-label">
|
||||||
|
{{ $t('commonTable.refHtmlUploadShort') }}
|
||||||
|
<em v-if="uploadedReferencesCount" class="zy-toolbar-badge">({{ uploadedReferencesCount }})</em>
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
|
</li>
|
||||||
|
<li
|
||||||
|
v-if="zyModeEnabled"
|
||||||
|
@click="handleExportManuscriptWord"
|
||||||
|
class="zy-toolbar-btn"
|
||||||
|
:class="{ 'zy-toolbar-btn--ready': uploadedReferencesCount }"
|
||||||
|
>
|
||||||
|
<el-tooltip
|
||||||
|
:content="uploadedReferencesCount ? $t('commonTable.exportWordWithUploadedRefs', { n: uploadedReferencesCount }) : $t('commonTable.exportWord')"
|
||||||
|
placement="bottom"
|
||||||
|
>
|
||||||
|
<span class="zy-toolbar-btn-inner">
|
||||||
|
<i
|
||||||
|
class="el-icon-download"
|
||||||
|
v-if="!exportingManuscriptWord"
|
||||||
|
></i>
|
||||||
|
<i class="el-icon-loading" v-else></i>
|
||||||
|
<span class="zy-toolbar-label">{{ $t('commonTable.exportWordShort') }}</span>
|
||||||
|
</span>
|
||||||
|
</el-tooltip>
|
||||||
</li>
|
</li>
|
||||||
<li
|
<li
|
||||||
v-if="zyModeEnabled"
|
v-if="zyModeEnabled"
|
||||||
@click="handleOpenCitationRelevance"
|
@click="handleOpenCitationRelevance"
|
||||||
class="base-font-size base-bg-imp base-padding-all"
|
class="zy-toolbar-btn"
|
||||||
:style="{ '--f-s': '12px', '--f-c': '#333' }"
|
|
||||||
>
|
>
|
||||||
<i
|
<el-tooltip :content="$t('commonTable.citeRelevanceDetect')" placement="bottom">
|
||||||
class="el-icon-connection base-margin"
|
<span class="zy-toolbar-btn-inner">
|
||||||
:style="{ '--m-r': '2px' }"
|
<i
|
||||||
v-if="!citationRelevanceLoading"
|
class="el-icon-connection"
|
||||||
></i>
|
v-if="!citationRelevanceLoading"
|
||||||
<i
|
></i>
|
||||||
class="el-icon-loading base-margin"
|
<i class="el-icon-loading" v-else></i>
|
||||||
:style="{ '--m-r': '2px' }"
|
<span class="zy-toolbar-label">{{ $t('commonTable.citeRelevanceDetectShort') }}</span>
|
||||||
v-else
|
</span>
|
||||||
></i>
|
</el-tooltip>
|
||||||
{{ $t('commonTable.citeRelevanceDetect') }}
|
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
@@ -1008,6 +1050,43 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
|
<el-dialog
|
||||||
|
append-to-body
|
||||||
|
destroy-on-close
|
||||||
|
:title="$t('commonTable.refHtmlPasteTitle')"
|
||||||
|
:visible.sync="referencesPasteVisible"
|
||||||
|
width="960px"
|
||||||
|
:close-on-click-modal="false"
|
||||||
|
>
|
||||||
|
<p style="margin: 0 0 12px; color: #606266; font-size: 13px; line-height: 1.6">
|
||||||
|
{{ $t('commonTable.refHtmlPasteTip') }}
|
||||||
|
</p>
|
||||||
|
<el-input
|
||||||
|
v-model="referencesPasteText"
|
||||||
|
type="textarea"
|
||||||
|
:rows="18"
|
||||||
|
:placeholder="$t('commonTable.refHtmlPastePlaceholder')"
|
||||||
|
@input="updateReferencesPastePreview"
|
||||||
|
></el-input>
|
||||||
|
<p v-if="referencesPasteText" style="margin: 10px 0 0; font-size: 13px" :style="{ color: referencesPasteCount > 0 ? '#67C23A' : '#E6A23C' }">
|
||||||
|
{{ referencesPastePreviewText }}
|
||||||
|
</p>
|
||||||
|
<span slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="referencesPasteVisible = false">{{ $t('commonTable.refHtmlPasteCancel') }}</el-button>
|
||||||
|
<el-button @click="triggerReferencesHtmlFileImport">{{ $t('commonTable.refHtmlImportHtml') }}</el-button>
|
||||||
|
<el-button type="primary" :loading="referencesUploadLoading" @click="confirmReferencesUpload">
|
||||||
|
{{ $t('commonTable.refHtmlUploadConfirm') }}
|
||||||
|
</el-button>
|
||||||
|
</span>
|
||||||
|
</el-dialog>
|
||||||
|
<input
|
||||||
|
ref="referencesHtmlFileInput"
|
||||||
|
type="file"
|
||||||
|
accept=".html,.htm,.txt,text/html,text/plain"
|
||||||
|
style="display: none"
|
||||||
|
@change="handleReferencesFileUpload"
|
||||||
|
/>
|
||||||
<div class="rail" ref="rail" v-if="isEditComment">
|
<div class="rail" ref="rail" v-if="isEditComment">
|
||||||
<div
|
<div
|
||||||
v-for="(m, i) in markers"
|
v-for="(m, i) in markers"
|
||||||
@@ -1101,6 +1180,15 @@ import { debounce, throttle } from '@/common/js/debounce';
|
|||||||
import { tableStyle, commonWordStyle } from '@/utils/tinymceStyles';
|
import { tableStyle, commonWordStyle } from '@/utils/tinymceStyles';
|
||||||
import LatexDataPanel from './LatexDataPanel.vue';
|
import LatexDataPanel from './LatexDataPanel.vue';
|
||||||
import { downloadManuscriptWord, fetchManuscriptReferenceList } from '@/utils/exportManuscriptWord';
|
import { downloadManuscriptWord, fetchManuscriptReferenceList } from '@/utils/exportManuscriptWord';
|
||||||
|
import {
|
||||||
|
buildReferencesCopyText,
|
||||||
|
buildReferencesHtmlLabels,
|
||||||
|
copyReferencesToClipboard,
|
||||||
|
countParsedReferences,
|
||||||
|
downloadReferencesEditableHtml,
|
||||||
|
parseReferencesBulkContent,
|
||||||
|
parseReferencesEditableHtml
|
||||||
|
} from '@/utils/manuscriptReferenceHtml';
|
||||||
import {
|
import {
|
||||||
buildCitationReviewQueue,
|
buildCitationReviewQueue,
|
||||||
buildCitationReviewHtmlLabels,
|
buildCitationReviewHtmlLabels,
|
||||||
@@ -1232,6 +1320,14 @@ export default {
|
|||||||
scrollPosition: 0,
|
scrollPosition: 0,
|
||||||
wordList: [],
|
wordList: [],
|
||||||
exportingManuscriptWord: false,
|
exportingManuscriptWord: false,
|
||||||
|
referencesCopyLoading: false,
|
||||||
|
referencesHtmlLoading: false,
|
||||||
|
referencesUploadLoading: false,
|
||||||
|
uploadedReferenceHtmlItems: null,
|
||||||
|
uploadedReferencesCount: 0,
|
||||||
|
referencesPasteVisible: false,
|
||||||
|
referencesPasteText: '',
|
||||||
|
referencesPasteCount: 0,
|
||||||
citationRelevanceLoading: false,
|
citationRelevanceLoading: false,
|
||||||
manuscriptReferences: [],
|
manuscriptReferences: [],
|
||||||
proofreadingList: [],
|
proofreadingList: [],
|
||||||
@@ -1361,6 +1457,15 @@ export default {
|
|||||||
zyModeEnabled() {
|
zyModeEnabled() {
|
||||||
return isZyModeEnabled(this.$route);
|
return isZyModeEnabled(this.$route);
|
||||||
},
|
},
|
||||||
|
referencesPastePreviewText() {
|
||||||
|
if (!this.referencesPasteText) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (this.referencesPasteCount > 0) {
|
||||||
|
return this.$t('commonTable.refHtmlPasteParsed', { n: this.referencesPasteCount });
|
||||||
|
}
|
||||||
|
return this.$t('commonTable.refHtmlPasteParseEmpty');
|
||||||
|
},
|
||||||
sortedProofreadingList() {
|
sortedProofreadingList() {
|
||||||
const order = [2, 1, 3];
|
const order = [2, 1, 3];
|
||||||
const rank = { 2: 0, 1: 1, 3: 2 };
|
const rank = { 2: 0, 1: 1, 3: 2 };
|
||||||
@@ -1496,6 +1601,164 @@ export default {
|
|||||||
this.editors = {};
|
this.editors = {};
|
||||||
},
|
},
|
||||||
methods: {
|
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() {
|
async handleExportManuscriptWord() {
|
||||||
if (this.exportingManuscriptWord) {
|
if (this.exportingManuscriptWord) {
|
||||||
return;
|
return;
|
||||||
@@ -1506,19 +1769,32 @@ export default {
|
|||||||
}
|
}
|
||||||
this.exportingManuscriptWord = true;
|
this.exportingManuscriptWord = true;
|
||||||
try {
|
try {
|
||||||
|
const hasUploaded =
|
||||||
|
this.uploadedReferenceHtmlItems && this.uploadedReferenceHtmlItems.length;
|
||||||
await downloadManuscriptWord(this.wordList, this.mediaUrl, 'manuscript', {
|
await downloadManuscriptWord(this.wordList, this.mediaUrl, 'manuscript', {
|
||||||
fetchReferences: true,
|
fetchReferences: !hasUploaded,
|
||||||
|
referenceHtmlItems: hasUploaded ? this.uploadedReferenceHtmlItems : null,
|
||||||
apiClient: this.$api,
|
apiClient: this.$api,
|
||||||
articleId: this.articleId,
|
articleId: this.articleId,
|
||||||
pArticleId: this.pArticleId
|
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) {
|
} catch (err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
if (err && err.message === 'NO_CONTENT') {
|
if (err && err.message === 'NO_CONTENT') {
|
||||||
this.$message.warning(this.$t('commonTable.exportManuscriptEmpty') || 'No content to export.');
|
this.$message.warning(this.$t('commonTable.exportManuscriptEmpty') || 'No content to export.');
|
||||||
} else {
|
} 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 {
|
} finally {
|
||||||
this.exportingManuscriptWord = false;
|
this.exportingManuscriptWord = false;
|
||||||
@@ -1539,11 +1815,7 @@ export default {
|
|||||||
|
|
||||||
this.citationRelevanceLoading = true;
|
this.citationRelevanceLoading = true;
|
||||||
try {
|
try {
|
||||||
let references = this.manuscriptReferences;
|
const references = await this.loadManuscriptReferences(false);
|
||||||
if (!references || !references.length) {
|
|
||||||
references = await fetchManuscriptReferenceList(this.$api, this.articleId, this.pArticleId);
|
|
||||||
this.manuscriptReferences = references || [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const items = buildCitationReviewQueue(this.wordList, this.manuscriptReferences);
|
const items = buildCitationReviewQueue(this.wordList, this.manuscriptReferences);
|
||||||
if (!items.length) {
|
if (!items.length) {
|
||||||
@@ -2607,6 +2879,14 @@ export default {
|
|||||||
this.selectedIds = [];
|
this.selectedIds = [];
|
||||||
this.$forceUpdate();
|
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() {
|
onEdit() {
|
||||||
this.$emit('onEdit', this.currentId);
|
this.$emit('onEdit', this.currentId);
|
||||||
},
|
},
|
||||||
@@ -4378,6 +4658,52 @@ export default {
|
|||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
height: auto;
|
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 {
|
.operateBox {
|
||||||
width: auto;
|
width: auto;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -556,6 +556,16 @@
|
|||||||
>We have detected updates to the reference content. You need to click the "Automatic parsing" button to
|
>We have detected updates to the reference content. You need to click the "Automatic parsing" button to
|
||||||
recognize them.</span
|
recognize them.</span
|
||||||
>
|
>
|
||||||
|
<el-button
|
||||||
|
v-if="zyModeEnabled && SourceType === 'book'"
|
||||||
|
type="primary"
|
||||||
|
plain
|
||||||
|
size="mini"
|
||||||
|
style="margin-top: 8px"
|
||||||
|
@click="parseBookReferenceZy"
|
||||||
|
>
|
||||||
|
{{ $t('commonTable.parseBookReference') }}
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
@@ -898,6 +908,8 @@
|
|||||||
<script>
|
<script>
|
||||||
import VueUeditorWrap from 'vue-ueditor-wrap'; // ES6 Module
|
import VueUeditorWrap from 'vue-ueditor-wrap'; // ES6 Module
|
||||||
import ReferenceSearchLinks from '@/components/page/components/ReferenceSearchLinks.vue';
|
import ReferenceSearchLinks from '@/components/page/components/ReferenceSearchLinks.vue';
|
||||||
|
import { isZyModeEnabled } from '@/utils/zyMode';
|
||||||
|
import { parseBookReferenceContent } from '@/utils/parseBookReference';
|
||||||
|
|
||||||
/** 单条引用 records[].status:0 待检测 2 已完成 3 检测失败 */
|
/** 单条引用 records[].status:0 待检测 2 已完成 3 检测失败 */
|
||||||
const REF_RELEVANCE_RECORD_STATUS_PENDING = 0;
|
const REF_RELEVANCE_RECORD_STATUS_PENDING = 0;
|
||||||
@@ -1072,6 +1084,9 @@ export default {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
zyModeEnabled() {
|
||||||
|
return isZyModeEnabled(this.$route);
|
||||||
|
},
|
||||||
showRefRelevanceToolbar() {
|
showRefRelevanceToolbar() {
|
||||||
return this.role === 'editor' && this.showB_step === 2 && !this.refRelevanceStatusLoading;
|
return this.role === 'editor' && this.showB_step === 2 && !this.refRelevanceStatusLoading;
|
||||||
},
|
},
|
||||||
@@ -1542,6 +1557,27 @@ export default {
|
|||||||
this.$message.error(err);
|
this.$message.error(err);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
parseBookReferenceZy() {
|
||||||
|
const content = String(this.refenceForm.content || '').trim();
|
||||||
|
if (!content) {
|
||||||
|
this.$message.error(this.$t('commonTable.parseBookReferenceContentEmpty'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const parsed = parseBookReferenceContent(content);
|
||||||
|
if (!parsed || (!parsed.author && !parsed.title && !parsed.dateno && !parsed.isbn)) {
|
||||||
|
this.$message.warning(this.$t('commonTable.parseBookReferenceEmpty'));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.SourceType = 'book';
|
||||||
|
this.refenceForm.refer_type = 'book';
|
||||||
|
this.refenceForm.author = parsed.author || '';
|
||||||
|
this.refenceForm.title = parsed.title || '';
|
||||||
|
this.refenceForm.dateno = parsed.dateno || '';
|
||||||
|
this.refenceForm.isbn = parsed.isbn || '';
|
||||||
|
this.isShowParsing = false;
|
||||||
|
this.isShowParsingData = true;
|
||||||
|
this.$message.success(this.$t('commonTable.parseBookReferenceSuccess'));
|
||||||
|
},
|
||||||
|
|
||||||
getRefData(id) {
|
getRefData(id) {
|
||||||
return this.$api
|
return this.$api
|
||||||
|
|||||||
@@ -71,7 +71,14 @@
|
|||||||
<span class="value time">{{ scope.row.ctime_text ? scope.row.ctime_text : $t('expertDatabase.emptyMark') }}</span>
|
<span class="value time">{{ scope.row.ctime_text ? scope.row.ctime_text : $t('expertDatabase.emptyMark') }}</span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<span class="custom-tag">{{ scope.row.state_text }}</span>
|
<el-tooltip
|
||||||
|
v-if="shouldShowStateTag(scope.row)"
|
||||||
|
:content="getLastContactTooltip(scope.row)"
|
||||||
|
placement="top"
|
||||||
|
effect="dark"
|
||||||
|
>
|
||||||
|
<span class="custom-tag">{{ scope.row.state_text }}</span>
|
||||||
|
</el-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -289,6 +296,20 @@ export default {
|
|||||||
getUnsubscribeValue(row) {
|
getUnsubscribeValue(row) {
|
||||||
return this.normalizeUnsubscribeValue(row);
|
return this.normalizeUnsubscribeValue(row);
|
||||||
},
|
},
|
||||||
|
shouldShowStateTag(row) {
|
||||||
|
const text = String((row && row.state_text) || '').trim();
|
||||||
|
if (!text || text.indexOf('待联系') !== -1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
getLastContactTooltip(row) {
|
||||||
|
const time = row && row.ltime_text ? String(row.ltime_text).trim() : '';
|
||||||
|
if (time) {
|
||||||
|
return this.$t('expertDatabase.lastContactTime', { time });
|
||||||
|
}
|
||||||
|
return this.$t('expertDatabase.lastContactTimeEmpty');
|
||||||
|
},
|
||||||
async handleUnsubscribeSwitch(row, checked) {
|
async handleUnsubscribeSwitch(row, checked) {
|
||||||
const expertId = this.getExpertId(row);
|
const expertId = this.getExpertId(row);
|
||||||
if (!expertId) {
|
if (!expertId) {
|
||||||
@@ -432,6 +453,7 @@ export default {
|
|||||||
color: #ce4f15;
|
color: #ce4f15;
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
|
cursor: default;
|
||||||
}
|
}
|
||||||
.info-row {
|
.info-row {
|
||||||
margin-bottom: 4px;
|
margin-bottom: 4px;
|
||||||
|
|||||||
@@ -44,8 +44,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<el-table :data="tableData" border stripe class="table" ref="multipleTable" @sort-change="changeSort"
|
<el-table :data="tableData" border stripe class="table" ref="multipleTable" v-loading="listLoading"
|
||||||
header-cell-class-name="table-header">
|
@sort-change="changeSort" header-cell-class-name="table-header">
|
||||||
<el-table-column label="Basic Information">
|
<el-table-column label="Basic Information">
|
||||||
<template slot-scope="scope">
|
<template slot-scope="scope">
|
||||||
<p class="tab_tie_col">
|
<p class="tab_tie_col">
|
||||||
@@ -147,8 +147,16 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<el-pagination background layout="total, prev, pager, next" :current-page="query.pageIndex"
|
<el-pagination
|
||||||
:page-size="query.pageSize" :total="Total" @current-change="handlePageChange"></el-pagination>
|
background
|
||||||
|
layout="sizes, total, prev, pager, next"
|
||||||
|
:current-page="query.pageIndex"
|
||||||
|
:page-size="query.pageSize"
|
||||||
|
:page-sizes="[10, 15, 20, 50, 100]"
|
||||||
|
:total="Total"
|
||||||
|
@size-change="handleSizeChange"
|
||||||
|
@current-change="handlePageChange"
|
||||||
|
></el-pagination>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -266,6 +274,8 @@ import bus from '../common/bus'
|
|||||||
order_remark: 0
|
order_remark: 0
|
||||||
},
|
},
|
||||||
Total: 0,
|
Total: 0,
|
||||||
|
listLoading: false,
|
||||||
|
paginationSizeChanging: false,
|
||||||
df_jour: [],
|
df_jour: [],
|
||||||
df_year: [],
|
df_year: [],
|
||||||
list_year: [{
|
list_year: [{
|
||||||
@@ -400,23 +410,43 @@ import bus from '../common/bus'
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 获取青年科学家列表数据
|
// 获取青年科学家列表数据
|
||||||
|
buildListParams() {
|
||||||
|
return {
|
||||||
|
journal_id: this.query.journal_id,
|
||||||
|
type: this.query.type,
|
||||||
|
year: this.query.year,
|
||||||
|
pageIndex: Number(this.query.pageIndex) || 1,
|
||||||
|
pageSize: Number(this.query.pageSize) || 15,
|
||||||
|
keywords: this.query.keywords || '',
|
||||||
|
fieldkey: this.query.fieldkey || '',
|
||||||
|
order_remark: this.query.order_remark || 0
|
||||||
|
};
|
||||||
|
},
|
||||||
getDate() {
|
getDate() {
|
||||||
this.$api
|
const params = this.buildListParams();
|
||||||
.post('api/User/getYboardlist', this.query)
|
this.listLoading = true;
|
||||||
|
return this.$api
|
||||||
|
.post('api/User/getYboardlist', params)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
if (res.code == 0) {
|
if (res.code == 0) {
|
||||||
console.log(res.data.yboards,'zheli');
|
this.tableData = res.data.yboards || [];
|
||||||
this.tableData = res.data.yboards;
|
|
||||||
for (var i = 0; i < this.tableData.length; i++) {
|
for (var i = 0; i < this.tableData.length; i++) {
|
||||||
this.getScoreData(i, this.tableData[i].score)
|
this.getScoreData(i, this.tableData[i].score);
|
||||||
}
|
}
|
||||||
this.Total = res.data.count;
|
this.Total = res.data.count || 0;
|
||||||
} else {
|
} else {
|
||||||
|
this.tableData = [];
|
||||||
|
this.Total = 0;
|
||||||
this.$message.error(res.msg);
|
this.$message.error(res.msg);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
|
this.tableData = [];
|
||||||
|
this.Total = 0;
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.listLoading = false;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -671,9 +701,23 @@ import bus from '../common/bus'
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// 分页导航
|
// 分页:每页条数
|
||||||
|
handleSizeChange(size) {
|
||||||
|
this.paginationSizeChanging = true;
|
||||||
|
this.query.pageSize = size;
|
||||||
|
this.query.pageIndex = 1;
|
||||||
|
this.getDate().finally(() => {
|
||||||
|
this.$nextTick(() => {
|
||||||
|
this.paginationSizeChanging = false;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
// 分页:页码
|
||||||
handlePageChange(val) {
|
handlePageChange(val) {
|
||||||
this.$set(this.query, 'pageIndex', val);
|
if (this.paginationSizeChanging) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.query.pageIndex = val;
|
||||||
this.getDate();
|
this.getDate();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { AlignmentType, ExternalHyperlink, LineRuleType, Packer, Paragraph, TextRun } from 'docx';
|
import { AlignmentType, ExternalHyperlink, LineRuleType, Packer, Paragraph, TextRun } from 'docx';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
import {
|
import {
|
||||||
|
appendFigureTitleParagraphs,
|
||||||
appendTableBlockEmptyLines,
|
appendTableBlockEmptyLines,
|
||||||
createTableParagraph,
|
createTableParagraph,
|
||||||
createWordDocument,
|
createWordDocument,
|
||||||
@@ -93,22 +94,7 @@ function buildFigureWordChildren(item, mediaUrl) {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (prepared.title) {
|
if (prepared.title) {
|
||||||
splitHtmlSegments(prepared.title).forEach((segment) => {
|
appendFigureTitleParagraphs(children, prepared.title);
|
||||||
children.push(
|
|
||||||
createTableParagraph(
|
|
||||||
[
|
|
||||||
new TextRun({
|
|
||||||
text: htmlToPlainText(segment),
|
|
||||||
bold: true,
|
|
||||||
color: TITLE_COLOR,
|
|
||||||
font: FONT_NAME,
|
|
||||||
size: TABLE_FONT_SIZE
|
|
||||||
})
|
|
||||||
],
|
|
||||||
AlignmentType.JUSTIFIED
|
|
||||||
)
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prepared.note) {
|
if (prepared.note) {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ import {
|
|||||||
WidthType
|
WidthType
|
||||||
} from 'docx';
|
} from 'docx';
|
||||||
import { saveAs } from 'file-saver';
|
import { saveAs } from 'file-saver';
|
||||||
|
import JSZip from 'jszip';
|
||||||
import { TableUtils } from '@/common/js/TableUtils';
|
import { TableUtils } from '@/common/js/TableUtils';
|
||||||
import { isMathFormulaTableRecord } from '@/utils/mathFormulaModule';
|
import { isMathFormulaTableRecord } from '@/utils/mathFormulaModule';
|
||||||
|
|
||||||
@@ -28,6 +29,10 @@ const FONT_NAME = 'Charis SIL';
|
|||||||
/** 六号 = 7.5pt */
|
/** 六号 = 7.5pt */
|
||||||
const TABLE_FONT_SIZE = 15;
|
const TABLE_FONT_SIZE = 15;
|
||||||
const TITLE_FONT_SIZE = TABLE_FONT_SIZE;
|
const TITLE_FONT_SIZE = TABLE_FONT_SIZE;
|
||||||
|
/** Word「为字体调整字间距」:1 磅或更大(w:kern 以半磅为单位) */
|
||||||
|
const FONT_KERN_MIN_1PT = 2;
|
||||||
|
/** Word「为字体调整字间距」:小二(18pt)或更大 */
|
||||||
|
const FONT_KERN_MIN_XIAO_ER = 36;
|
||||||
|
|
||||||
function cmToTwips(cm) {
|
function cmToTwips(cm) {
|
||||||
return Math.round((cm * 1440) / 2.54);
|
return Math.round((cm * 1440) / 2.54);
|
||||||
@@ -73,7 +78,8 @@ const WORD_DOCUMENT_STYLES = {
|
|||||||
},
|
},
|
||||||
run: {
|
run: {
|
||||||
font: FONT_NAME,
|
font: FONT_NAME,
|
||||||
size: TABLE_FONT_SIZE
|
size: TABLE_FONT_SIZE,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -85,7 +91,8 @@ const WORD_DOCUMENT_STYLES = {
|
|||||||
spacing: WORD_PARAGRAPH_SPACING,
|
spacing: WORD_PARAGRAPH_SPACING,
|
||||||
run: {
|
run: {
|
||||||
font: FONT_NAME,
|
font: FONT_NAME,
|
||||||
size: TABLE_FONT_SIZE
|
size: TABLE_FONT_SIZE,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -99,7 +106,8 @@ const WORD_DOCUMENT_STYLES = {
|
|||||||
font: FONT_NAME,
|
font: FONT_NAME,
|
||||||
size: TITLE_FONT_SIZE,
|
size: TITLE_FONT_SIZE,
|
||||||
bold: true,
|
bold: true,
|
||||||
color: TITLE_COLOR
|
color: TITLE_COLOR,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -126,8 +134,7 @@ function createWordParagraph(children, options) {
|
|||||||
|
|
||||||
if (opts.title) {
|
if (opts.title) {
|
||||||
paragraphOptions.alignment = AlignmentType.CENTER;
|
paragraphOptions.alignment = AlignmentType.CENTER;
|
||||||
paragraphOptions.style = 'Heading1';
|
paragraphOptions.style = 'Normal';
|
||||||
paragraphOptions.outlineLevel = 0;
|
|
||||||
} else {
|
} else {
|
||||||
paragraphOptions.alignment = alignment;
|
paragraphOptions.alignment = alignment;
|
||||||
paragraphOptions.style = 'Normal';
|
paragraphOptions.style = 'Normal';
|
||||||
@@ -143,9 +150,134 @@ function appendTableBlockEmptyLines(children) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 表格标题段落:居中,大纲 1 级,固定行距 10pt,段前段后 0,无缩进 */
|
/** 表格标题段落:居中,固定行距 10pt,段前段后 0,无缩进(非大纲一级) */
|
||||||
function createTitleParagraph(children) {
|
function createTitleParagraph(children) {
|
||||||
return createWordParagraph(children, { title: true });
|
return createWordParagraph(children, { alignment: AlignmentType.CENTER });
|
||||||
|
}
|
||||||
|
|
||||||
|
function decodeTableCaptionXmlText(text) {
|
||||||
|
return String(text || '')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, "'")
|
||||||
|
.replace(/&/g, '&');
|
||||||
|
}
|
||||||
|
|
||||||
|
function extractTableCaptionParagraphPlainText(pInner) {
|
||||||
|
const parts = [];
|
||||||
|
String(pInner || '').replace(/<w:t(?:\s[^>]*)?>([\s\S]*?)<\/w:t>/g, function (_match, value) {
|
||||||
|
parts.push(decodeTableCaptionXmlText(value));
|
||||||
|
});
|
||||||
|
return parts.join('').replace(/[\u200B-\u200D\uFEFF]/g, '').trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function isBodyTableCaptionPlainText(text) {
|
||||||
|
return /^Table\s+\d+/i.test(String(text || '').trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyTableCaptionCenterParagraphPr(pInner) {
|
||||||
|
if (/<w:pPr>/.test(pInner)) {
|
||||||
|
let inner = pInner
|
||||||
|
.replace(/<w:jc\b[^>]*\/>/g, '')
|
||||||
|
.replace(/<w:jc\b[^>]*>[\s\S]*?<\/w:jc>/g, '');
|
||||||
|
return inner.replace(/<w:pPr>/, '<w:pPr><w:jc w:val="center"/>');
|
||||||
|
}
|
||||||
|
return '<w:pPr><w:jc w:val="center"/></w:pPr>' + pInner;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 正文表格标题(Table N…)紧挨 w:tbl 前的段落强制居中,不含首页元数据表格 */
|
||||||
|
export function patchBodyTableCaptionCenterToXml(xml) {
|
||||||
|
return xml.replace(/((?:<w:p\b[^>]*>[\s\S]*?<\/w:p>\s*)+)(\s*<w:tbl\b)/gi, function (match, paragraphGroup, tblOpen) {
|
||||||
|
const blocks = paragraphGroup.match(/<w:p\b[^>]*>[\s\S]*?<\/w:p>/gi) || [];
|
||||||
|
if (!blocks.length) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
const firstText = extractTableCaptionParagraphPlainText(
|
||||||
|
blocks[0].replace(/^<w:p\b[^>]*>/, '').replace(/<\/w:p>$/, '')
|
||||||
|
);
|
||||||
|
if (!isBodyTableCaptionPlainText(firstText)) {
|
||||||
|
return match;
|
||||||
|
}
|
||||||
|
|
||||||
|
const centered = blocks.map(function (block) {
|
||||||
|
return block.replace(/(<w:p\b[^>]*>)([\s\S]*?)(<\/w:p>)/, function (_full, open, inner, close) {
|
||||||
|
return open + applyTableCaptionCenterParagraphPr(inner) + close;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return centered.join('') + tblOpen;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 导出后处理:除首页表格外,正文表格标题居中 */
|
||||||
|
async function patchBodyTableCaptionCenter(blob) {
|
||||||
|
if (!blob) {
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
const zip = await JSZip.loadAsync(await blob.arrayBuffer());
|
||||||
|
const documentFile = zip.file('word/document.xml');
|
||||||
|
if (!documentFile) {
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
let xml = await documentFile.async('string');
|
||||||
|
if (!/\bTable\s+\d+/i.test(xml)) {
|
||||||
|
return blob;
|
||||||
|
}
|
||||||
|
|
||||||
|
xml = patchBodyTableCaptionCenterToXml(xml);
|
||||||
|
|
||||||
|
zip.file('word/document.xml', xml);
|
||||||
|
return zip.generateAsync({
|
||||||
|
type: 'blob',
|
||||||
|
mimeType: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 图片标题:与表格标题相同字号/颜色/段落,但两端对齐;末尾无句点则补 . */
|
||||||
|
function ensureFigureTitleTrailingPeriod(text) {
|
||||||
|
const trimmed = String(text || '').trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (trimmed.endsWith('.')) {
|
||||||
|
return trimmed;
|
||||||
|
}
|
||||||
|
return trimmed + '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
function createFigureTitleParagraph(children) {
|
||||||
|
return createWordParagraph(children, { alignment: AlignmentType.JUSTIFIED });
|
||||||
|
}
|
||||||
|
|
||||||
|
function appendFigureTitleParagraphs(children, titleHtml) {
|
||||||
|
const segments = splitHtmlSegments(titleHtml)
|
||||||
|
.map(function (segment) {
|
||||||
|
return htmlToPlainText(segment).trim();
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
if (!segments.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
segments.forEach(function (segment, index) {
|
||||||
|
const text =
|
||||||
|
index === segments.length - 1 ? ensureFigureTitleTrailingPeriod(segment) : segment;
|
||||||
|
children.push(
|
||||||
|
createFigureTitleParagraph([
|
||||||
|
new TextRun({
|
||||||
|
text: text,
|
||||||
|
bold: true,
|
||||||
|
color: TITLE_COLOR,
|
||||||
|
font: FONT_NAME,
|
||||||
|
size: TITLE_FONT_SIZE,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
|
})
|
||||||
|
])
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTableParagraph(children, alignment) {
|
function createTableParagraph(children, alignment) {
|
||||||
@@ -201,6 +333,42 @@ function isBlueElement(node) {
|
|||||||
return !!(node.classList && node.classList.contains('color-highlight'));
|
return !!(node.classList && node.classList.contains('color-highlight'));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 参考文献标号 [1] / [1, 2]:逗号后补空格(与 HTML 排版规则一致,重复 3 次) */
|
||||||
|
function normalizeCitationBracketCommaSpacing(text) {
|
||||||
|
return String(text || '').replace(/\[([^\]]*)\]/g, function (match, inner) {
|
||||||
|
let fixed = inner;
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
fixed = fixed.replace(/(\d),(\d)/g, '$1, $2');
|
||||||
|
}
|
||||||
|
return '[' + fixed + ']';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 匹配正文参考文献标号:\[[0-9, -\-\]{1,}\] */
|
||||||
|
const CITATION_BRACKET_PATTERN = /\[[0-9, \-]+\]/g;
|
||||||
|
|
||||||
|
function appendRunsForCitationText(text, style, appendRun) {
|
||||||
|
const normalized = normalizeCitationBracketCommaSpacing(text);
|
||||||
|
if (!normalized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let lastIndex = 0;
|
||||||
|
let match;
|
||||||
|
CITATION_BRACKET_PATTERN.lastIndex = 0;
|
||||||
|
while ((match = CITATION_BRACKET_PATTERN.exec(normalized)) !== null) {
|
||||||
|
if (match.index > lastIndex) {
|
||||||
|
appendRun(normalized.slice(lastIndex, match.index), style);
|
||||||
|
}
|
||||||
|
appendRun(match[0], Object.assign({}, style, { blue: true }));
|
||||||
|
lastIndex = match.index + match[0].length;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastIndex < normalized.length) {
|
||||||
|
appendRun(normalized.slice(lastIndex), style);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function htmlToTextRuns(html, options) {
|
function htmlToTextRuns(html, options) {
|
||||||
const { bold = false } = options || {};
|
const { bold = false } = options || {};
|
||||||
const runs = [];
|
const runs = [];
|
||||||
@@ -212,18 +380,42 @@ function htmlToTextRuns(html, options) {
|
|||||||
text: '',
|
text: '',
|
||||||
font: FONT_NAME,
|
font: FONT_NAME,
|
||||||
size: TABLE_FONT_SIZE,
|
size: TABLE_FONT_SIZE,
|
||||||
bold
|
bold,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof document === 'undefined') {
|
if (typeof document === 'undefined') {
|
||||||
|
const plainRuns = [];
|
||||||
|
appendRunsForCitationText(htmlToPlainText(raw), { bold, italics: false, sup: false, sub: false, blue: false }, function (
|
||||||
|
text,
|
||||||
|
style
|
||||||
|
) {
|
||||||
|
plainRuns.push(
|
||||||
|
new TextRun({
|
||||||
|
text,
|
||||||
|
font: FONT_NAME,
|
||||||
|
size: TABLE_FONT_SIZE,
|
||||||
|
bold: bold || style.bold,
|
||||||
|
italics: style.italic,
|
||||||
|
superScript: style.sup,
|
||||||
|
subScript: style.sub,
|
||||||
|
color: style.blue ? BLUE_COLOR : undefined,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
|
})
|
||||||
|
);
|
||||||
|
});
|
||||||
|
if (plainRuns.length) {
|
||||||
|
return plainRuns;
|
||||||
|
}
|
||||||
return [
|
return [
|
||||||
new TextRun({
|
new TextRun({
|
||||||
text: htmlToPlainText(raw),
|
text: htmlToPlainText(raw),
|
||||||
font: FONT_NAME,
|
font: FONT_NAME,
|
||||||
size: TABLE_FONT_SIZE,
|
size: TABLE_FONT_SIZE,
|
||||||
bold
|
bold,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -244,14 +436,19 @@ function htmlToTextRuns(html, options) {
|
|||||||
italics: style.italic,
|
italics: style.italic,
|
||||||
superScript: style.sup,
|
superScript: style.sup,
|
||||||
subScript: style.sub,
|
subScript: style.sub,
|
||||||
color: style.blue ? BLUE_COLOR : undefined
|
color: style.blue ? BLUE_COLOR : undefined,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function walk(node, style) {
|
function walk(node, style) {
|
||||||
if (node.nodeType === Node.TEXT_NODE) {
|
if (node.nodeType === Node.TEXT_NODE) {
|
||||||
appendRun(decodeHtmlEntities(node.textContent).replace(/\u00a0/g, ' '), style);
|
appendRunsForCitationText(
|
||||||
|
decodeHtmlEntities(node.textContent).replace(/\u00a0/g, ' '),
|
||||||
|
style,
|
||||||
|
appendRun
|
||||||
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (node.nodeType !== Node.ELEMENT_NODE) {
|
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||||||
@@ -304,7 +501,8 @@ function htmlToTextRuns(html, options) {
|
|||||||
text: htmlToPlainText(raw),
|
text: htmlToPlainText(raw),
|
||||||
font: FONT_NAME,
|
font: FONT_NAME,
|
||||||
size: TABLE_FONT_SIZE,
|
size: TABLE_FONT_SIZE,
|
||||||
bold
|
bold,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -360,7 +558,8 @@ function htmlToHeaderTextRuns(html) {
|
|||||||
text: formatHeaderWordSpacing(html),
|
text: formatHeaderWordSpacing(html),
|
||||||
font: FONT_NAME,
|
font: FONT_NAME,
|
||||||
size: TABLE_FONT_SIZE,
|
size: TABLE_FONT_SIZE,
|
||||||
bold: true
|
bold: true,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
})
|
})
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -525,7 +724,8 @@ function buildTableWordChildren(processedItem, options) {
|
|||||||
bold: true,
|
bold: true,
|
||||||
color: TITLE_COLOR,
|
color: TITLE_COLOR,
|
||||||
font: FONT_NAME,
|
font: FONT_NAME,
|
||||||
size: TITLE_FONT_SIZE
|
size: TITLE_FONT_SIZE,
|
||||||
|
kern: FONT_KERN_MIN_1PT
|
||||||
})
|
})
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
@@ -601,7 +801,8 @@ function sanitizeFileName(name) {
|
|||||||
|
|
||||||
export async function downloadTableWord(processedItem, fileName) {
|
export async function downloadTableWord(processedItem, fileName) {
|
||||||
const doc = buildTableWordDocument(processedItem);
|
const doc = buildTableWordDocument(processedItem);
|
||||||
const blob = await Packer.toBlob(doc);
|
let blob = await Packer.toBlob(doc);
|
||||||
|
blob = await patchBodyTableCaptionCenter(blob);
|
||||||
saveAs(blob, `${sanitizeFileName(fileName || processedItem.title)}.docx`);
|
saveAs(blob, `${sanitizeFileName(fileName || processedItem.title)}.docx`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,19 +821,26 @@ export async function downloadAllTablesWord(items, fileName) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const doc = buildAllTablesWordDocument(exportItems);
|
const doc = buildAllTablesWordDocument(exportItems);
|
||||||
const blob = await Packer.toBlob(doc);
|
let blob = await Packer.toBlob(doc);
|
||||||
|
blob = await patchBodyTableCaptionCenter(blob);
|
||||||
saveAs(blob, `${sanitizeFileName(fileName || 'tables')}.docx`);
|
saveAs(blob, `${sanitizeFileName(fileName || 'tables')}.docx`);
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
|
appendFigureTitleParagraphs,
|
||||||
appendTableBlockEmptyLines,
|
appendTableBlockEmptyLines,
|
||||||
buildTableWordChildren,
|
buildTableWordChildren,
|
||||||
|
createFigureTitleParagraph,
|
||||||
createTableParagraph,
|
createTableParagraph,
|
||||||
createWordDocument,
|
createWordDocument,
|
||||||
|
ensureFigureTitleTrailingPeriod,
|
||||||
|
FONT_KERN_MIN_1PT,
|
||||||
|
FONT_KERN_MIN_XIAO_ER,
|
||||||
FONT_NAME,
|
FONT_NAME,
|
||||||
htmlToPlainText,
|
htmlToPlainText,
|
||||||
htmlToTextRuns,
|
htmlToTextRuns,
|
||||||
PAGE_MARGINS,
|
PAGE_MARGINS,
|
||||||
|
patchBodyTableCaptionCenter,
|
||||||
splitHtmlSegments,
|
splitHtmlSegments,
|
||||||
TABLE_FONT_SIZE,
|
TABLE_FONT_SIZE,
|
||||||
TITLE_COLOR,
|
TITLE_COLOR,
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ export function getDualColumnWidthPt() {
|
|||||||
return Math.round(col1WidthTwips / 20);
|
return Math.round(col1WidthTwips / 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 版心宽度(pt),用于 H1 蓝色形状横跨整页 */
|
||||||
|
export function getTextAreaWidthPt() {
|
||||||
|
return Math.round(getTextAreaWidthTwips() / 20);
|
||||||
|
}
|
||||||
|
|
||||||
export function createWordSectionProperties(columnCount, isFirstSection) {
|
export function createWordSectionProperties(columnCount, isFirstSection) {
|
||||||
const properties = {
|
const properties = {
|
||||||
page: {
|
page: {
|
||||||
@@ -100,3 +105,52 @@ export function createWordSectionProperties(columnCount, isFirstSection) {
|
|||||||
|
|
||||||
return properties;
|
return properties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** OOXML:双栏 w:cols(与 createDualColumnProperties 一致) */
|
||||||
|
export function buildDualColumnSectPrColsXml() {
|
||||||
|
const textWidth = getTextAreaWidthTwips();
|
||||||
|
const totalChars = getDualColumnTotalChars();
|
||||||
|
const col1Width = Math.round((textWidth * MANUSCRIPT_DUAL_COLUMN.col1WidthChars) / totalChars);
|
||||||
|
const colSpace = Math.round((textWidth * MANUSCRIPT_DUAL_COLUMN.spaceChars) / totalChars);
|
||||||
|
const col2Width = textWidth - col1Width - colSpace;
|
||||||
|
|
||||||
|
return (
|
||||||
|
'<w:cols w:num="2" w:equalWidth="0" w:sep="0">' +
|
||||||
|
'<w:col w:w="' +
|
||||||
|
col1Width +
|
||||||
|
'" w:space="' +
|
||||||
|
colSpace +
|
||||||
|
'"/>' +
|
||||||
|
'<w:col w:w="' +
|
||||||
|
col2Width +
|
||||||
|
'"/>' +
|
||||||
|
'</w:cols>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 连续分节符:恢复正文双栏 */
|
||||||
|
export function buildDualColumnContinuousSectPrInner() {
|
||||||
|
const grid = getDocumentGridProperties();
|
||||||
|
return (
|
||||||
|
'<w:type w:val="continuous"/>' +
|
||||||
|
'<w:pgSz w:w="' +
|
||||||
|
PAGE_WIDTH_TWIPS +
|
||||||
|
'" w:h="' +
|
||||||
|
PAGE_HEIGHT_TWIPS +
|
||||||
|
'" w:orient="portrait"/>' +
|
||||||
|
'<w:pgMar w:top="' +
|
||||||
|
PAGE_MARGINS.top +
|
||||||
|
'" w:right="' +
|
||||||
|
PAGE_MARGINS.right +
|
||||||
|
'" w:bottom="' +
|
||||||
|
PAGE_MARGINS.bottom +
|
||||||
|
'" w:left="' +
|
||||||
|
PAGE_MARGINS.left +
|
||||||
|
'" w:header="708" w:footer="708" w:gutter="0"/>' +
|
||||||
|
'<w:pgNumType/>' +
|
||||||
|
buildDualColumnSectPrColsXml() +
|
||||||
|
'<w:docGrid w:linePitch="' +
|
||||||
|
grid.linePitch +
|
||||||
|
'"/>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { htmlToPlainText } from '@/utils/exportTableWord';
|
import { htmlToPlainText } from '@/utils/exportTableWord';
|
||||||
|
import { normalizeReferencePublicationDetails } from '@/utils/exportManuscriptWord';
|
||||||
|
|
||||||
|
const REF_COPY_DOI_COLOR = '0082AA';
|
||||||
|
const REF_COPY_FONT = 'Charis SIL, Georgia, Times New Roman, serif';
|
||||||
|
|
||||||
function escapeHtml(text) {
|
function escapeHtml(text) {
|
||||||
return String(text || '')
|
return String(text || '')
|
||||||
@@ -67,7 +71,7 @@ export function referenceRecordToEditableHtml(ref) {
|
|||||||
let html = stripAvailableAtSuffix(ref.refer_frag);
|
let html = stripAvailableAtSuffix(ref.refer_frag);
|
||||||
const link = normalizeReferenceLink(ref.doilink || getReferenceIsbnValue(ref));
|
const link = normalizeReferenceLink(ref.doilink || getReferenceIsbnValue(ref));
|
||||||
if (link) {
|
if (link) {
|
||||||
html += '<br>Available at:<br>' + escapeHtml(ref.doilink || ref.isbn || link);
|
html += '<br> Available at:<br>' + escapeHtml(ref.doilink || ref.isbn || link);
|
||||||
}
|
}
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
@@ -86,7 +90,7 @@ export function referenceRecordToEditableHtml(ref) {
|
|||||||
}
|
}
|
||||||
const isbnValue = getReferenceIsbnValue(ref);
|
const isbnValue = getReferenceIsbnValue(ref);
|
||||||
if (isbnValue) {
|
if (isbnValue) {
|
||||||
parts.push('Available at:<br>ISBN: ' + escapeHtml(isbnValue));
|
parts.push(' Available at:<br>ISBN: ' + escapeHtml(isbnValue));
|
||||||
}
|
}
|
||||||
return parts.join('');
|
return parts.join('');
|
||||||
}
|
}
|
||||||
@@ -95,7 +99,7 @@ export function referenceRecordToEditableHtml(ref) {
|
|||||||
let html = stripAvailableAtSuffix(ref.refer_frag);
|
let html = stripAvailableAtSuffix(ref.refer_frag);
|
||||||
const link = normalizeReferenceLink(ref.doilink);
|
const link = normalizeReferenceLink(ref.doilink);
|
||||||
if (link) {
|
if (link) {
|
||||||
html += '<br>Available at:<br>' + escapeHtml(ref.doilink || link);
|
html += '<br> Available at:<br>' + escapeHtml(ref.doilink || link);
|
||||||
}
|
}
|
||||||
return html;
|
return html;
|
||||||
}
|
}
|
||||||
@@ -114,7 +118,7 @@ export function referenceRecordToEditableHtml(ref) {
|
|||||||
}
|
}
|
||||||
const doiLink = normalizeReferenceLink(ref.doilink);
|
const doiLink = normalizeReferenceLink(ref.doilink);
|
||||||
if (doiLink) {
|
if (doiLink) {
|
||||||
parts.push('Available at:<br>' + escapeHtml(ref.doilink || doiLink));
|
parts.push(' Available at:<br>' + escapeHtml(ref.doilink || doiLink));
|
||||||
}
|
}
|
||||||
|
|
||||||
return parts.join('');
|
return parts.join('');
|
||||||
@@ -128,18 +132,8 @@ export function buildReferencesEditableHtml(references, labels) {
|
|||||||
const saveTip = L.saveTip || '';
|
const saveTip = L.saveTip || '';
|
||||||
const downloadBtn = L.downloadEdited || 'Download edited HTML';
|
const downloadBtn = L.downloadEdited || 'Download edited HTML';
|
||||||
const refTitle = L.referencesTitle || 'References';
|
const refTitle = L.referencesTitle || 'References';
|
||||||
|
const bulkHint = L.bulkHint || '';
|
||||||
let listHtml = '';
|
const bulkContent = buildReferencesBulkHtml(list);
|
||||||
list.forEach(function (ref, index) {
|
|
||||||
const referType = String(ref.refer_type || 'journal').toLowerCase();
|
|
||||||
const content = referenceRecordToEditableHtml(ref);
|
|
||||||
listHtml +=
|
|
||||||
'<li class="ref-item" contenteditable="true" spellcheck="false"' +
|
|
||||||
' data-ref-index="' + index + '"' +
|
|
||||||
' data-ref-type="' + escapeHtml(referType) + '">' +
|
|
||||||
content +
|
|
||||||
'</li>';
|
|
||||||
});
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
'<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">' +
|
'<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">' +
|
||||||
@@ -153,12 +147,11 @@ export function buildReferencesEditableHtml(references, labels) {
|
|||||||
'.intro{color:#606266;font-size:14px;margin-bottom:12px;white-space:pre-wrap}' +
|
'.intro{color:#606266;font-size:14px;margin-bottom:12px;white-space:pre-wrap}' +
|
||||||
'.actions{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}' +
|
'.actions{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:8px}' +
|
||||||
'.btn{border:1px solid #409eff;background:#409eff;color:#fff;border-radius:4px;padding:8px 16px;font-size:13px;cursor:pointer}' +
|
'.btn{border:1px solid #409eff;background:#409eff;color:#fff;border-radius:4px;padding:8px 16px;font-size:13px;cursor:pointer}' +
|
||||||
'.btn.plain{background:#ecf5ff;color:#409eff}' +
|
|
||||||
'.ref-panel{background:#fff;border-radius:8px;padding:20px 24px;box-shadow:0 1px 4px rgba(0,0,0,.08);font-size:12px}' +
|
'.ref-panel{background:#fff;border-radius:8px;padding:20px 24px;box-shadow:0 1px 4px rgba(0,0,0,.08);font-size:12px}' +
|
||||||
'.ref-list{margin:0;padding-left:1.4em;list-style:decimal}' +
|
'.ref-bulk{min-height:480px;padding:12px 14px;outline:none;border:1px solid #dcdfe6;border-radius:4px;line-height:1.6;white-space:pre-wrap;word-break:break-word}' +
|
||||||
'.ref-item{margin-bottom:10px;padding:4px 0;outline:none;min-height:1.4em}' +
|
'.ref-bulk:focus{border-color:#409eff;box-shadow:0 0 0 2px rgba(64,158,255,.15)}' +
|
||||||
'.ref-item:focus{background:#fafcff;box-shadow:inset 0 0 0 1px #c6e2ff;border-radius:2px}' +
|
'.ref-bulk i,.ref-bulk em{font-style:italic}' +
|
||||||
'.ref-item i,.ref-item em{font-style:italic}' +
|
'.bulk-hint{margin:0 0 10px;color:#909399;font-size:12px}' +
|
||||||
'</style></head><body><div class="page">' +
|
'</style></head><body><div class="page">' +
|
||||||
'<div class="header"><h1>' + escapeHtml(refTitle) + '</h1>' +
|
'<div class="header"><h1>' + escapeHtml(refTitle) + '</h1>' +
|
||||||
'<div class="intro">' + escapeHtml(intro) + '</div>' +
|
'<div class="intro">' + escapeHtml(intro) + '</div>' +
|
||||||
@@ -167,9 +160,11 @@ export function buildReferencesEditableHtml(references, labels) {
|
|||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class="intro" style="font-size:12px;color:#909399">' + escapeHtml(saveTip) + '</div>' +
|
'<div class="intro" style="font-size:12px;color:#909399">' + escapeHtml(saveTip) + '</div>' +
|
||||||
'</div>' +
|
'</div>' +
|
||||||
'<div class="ref-panel"><ol class="ref-list" id="ref-list">' +
|
'<div class="ref-panel">' +
|
||||||
listHtml +
|
'<p class="bulk-hint">' + escapeHtml(bulkHint) + '</p>' +
|
||||||
'</ol></div></div>' +
|
'<div id="ref-bulk" class="ref-bulk" contenteditable="true" spellcheck="false">' +
|
||||||
|
bulkContent +
|
||||||
|
'</div></div></div>' +
|
||||||
'<script>(function(){' +
|
'<script>(function(){' +
|
||||||
'function serializePage(){var clone=document.documentElement.cloneNode(true);' +
|
'function serializePage(){var clone=document.documentElement.cloneNode(true);' +
|
||||||
'var btn=clone.querySelector("#download-edited-btn");if(btn){btn.remove();}' +
|
'var btn=clone.querySelector("#download-edited-btn");if(btn){btn.remove();}' +
|
||||||
@@ -184,6 +179,203 @@ export function buildReferencesEditableHtml(references, labels) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** 整段参考文献 HTML(用于可编辑区初始内容) */
|
||||||
|
export function buildReferencesBulkHtml(references) {
|
||||||
|
const list = (references || []).filter(Boolean);
|
||||||
|
return list
|
||||||
|
.map(function (ref, index) {
|
||||||
|
const content = buildReferenceCopyItemHtml(ref);
|
||||||
|
if (!content) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return (index + 1) + '. ' + content;
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('<br><br>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function stripLeadingReferenceNumber(text) {
|
||||||
|
return String(text || '')
|
||||||
|
.replace(/^\s*(?:<[^>]+>\s*)*\[\d+\]\s*/i, '')
|
||||||
|
.replace(/^\s*(?:<[^>]+>\s*)*\d+\.\s*/i, '')
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
function unwrapReferenceHtmlSegment(segment) {
|
||||||
|
let seg = String(segment || '').trim();
|
||||||
|
if (!seg) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
seg = seg.replace(/^<p[^>]*>/i, '').replace(/<\/p>$/i, '');
|
||||||
|
seg = seg.replace(/^<div[^>]*>/i, '').replace(/<\/div>$/i, '');
|
||||||
|
return seg.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** contenteditable 编辑后常见 div 换行,统一成 br 便于拆分 */
|
||||||
|
function normalizeReferenceBulkHtml(html) {
|
||||||
|
return String(html || '')
|
||||||
|
.replace(/\r\n/g, '\n')
|
||||||
|
.replace(/<div>\s*<br\s*\/?>\s*<\/div>/gi, '<br><br>')
|
||||||
|
.replace(/<\/div>\s*<div[^>]*>/gi, '<br><br>')
|
||||||
|
.replace(/^<div[^>]*>/i, '')
|
||||||
|
.replace(/<\/div>$/i, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitReferenceSegmentsByBlockElements(raw) {
|
||||||
|
if (typeof DOMParser === 'undefined') {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = new DOMParser().parseFromString('<div id="ref-root">' + raw + '</div>', 'text/html');
|
||||||
|
const root = doc.getElementById('ref-root');
|
||||||
|
if (!root) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const directBlocks = Array.from(root.children).filter(function (el) {
|
||||||
|
const tag = el.tagName.toLowerCase();
|
||||||
|
return tag === 'p' || tag === 'div';
|
||||||
|
});
|
||||||
|
|
||||||
|
if (directBlocks.length > 1) {
|
||||||
|
return directBlocks
|
||||||
|
.map(function (node) {
|
||||||
|
return unwrapReferenceHtmlSegment(node.innerHTML || node.textContent || '');
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/<(?:p|div)\b/i.test(raw)) {
|
||||||
|
const blocks = doc.querySelectorAll('#ref-root p, #ref-root div');
|
||||||
|
if (blocks.length > 1) {
|
||||||
|
return Array.from(blocks)
|
||||||
|
.map(function (node) {
|
||||||
|
return unwrapReferenceHtmlSegment(node.innerHTML || node.textContent || '');
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitReferenceSegments(body) {
|
||||||
|
let raw = normalizeReferenceBulkHtml(body);
|
||||||
|
raw = String(raw || '').trim();
|
||||||
|
if (!raw) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const blockSegments = splitReferenceSegmentsByBlockElements(raw);
|
||||||
|
if (blockSegments.length > 1) {
|
||||||
|
return blockSegments;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/<br\s*\/?>\s*<br\s*\/?>/i.test(raw)) {
|
||||||
|
return raw.split(/<br\s*\/?>\s*<br\s*\/?>/i).map(function (part) {
|
||||||
|
return part.trim();
|
||||||
|
}).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
const normalized = raw.replace(/\r\n/g, '\n').replace(/<br\s*\/?>/gi, '\n');
|
||||||
|
|
||||||
|
if (/\n(?=\d+\.\s)/.test(normalized)) {
|
||||||
|
return normalized.split(/\n(?=\d+\.\s)/).map(function (part) {
|
||||||
|
return part.trim();
|
||||||
|
}).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/\n(?=\[\d+\])/.test(normalized)) {
|
||||||
|
return normalized.split(/\n(?=\[\d+\])/).map(function (part) {
|
||||||
|
return part.trim();
|
||||||
|
}).filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
const parts = normalized.split(/\n\s*\n+/);
|
||||||
|
if (parts.length > 1) {
|
||||||
|
const merged = [];
|
||||||
|
parts.forEach(function (part) {
|
||||||
|
const trimmed = part.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (/^\d+\.\s/.test(trimmed) || /^\[\d+\]/.test(trimmed) || !merged.length) {
|
||||||
|
merged.push(trimmed);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
merged[merged.length - 1] += '\n' + trimmed;
|
||||||
|
});
|
||||||
|
return merged.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
return blockSegments.length ? blockSegments : [raw];
|
||||||
|
}
|
||||||
|
|
||||||
|
function inferReferenceTypeFromHtml(html) {
|
||||||
|
const raw = String(html || '');
|
||||||
|
if (/ISBN\s*:/i.test(raw)) {
|
||||||
|
return 'book';
|
||||||
|
}
|
||||||
|
return 'journal';
|
||||||
|
}
|
||||||
|
|
||||||
|
function plainSegmentToReferenceHtml(segment) {
|
||||||
|
let seg = stripLeadingReferenceNumber(segment);
|
||||||
|
if (!seg) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
if (/<[a-z][\s\S]*>/i.test(seg)) {
|
||||||
|
return seg.replace(/\n/g, '<br>');
|
||||||
|
}
|
||||||
|
return escapeHtml(seg).replace(/\n/g, '<br>');
|
||||||
|
}
|
||||||
|
|
||||||
|
function segmentToReferenceItem(segment) {
|
||||||
|
const html = plainSegmentToReferenceHtml(segment);
|
||||||
|
if (!html) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
refer_type: inferReferenceTypeFromHtml(html),
|
||||||
|
html: html
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将整段参考文献内容拆分为 Word 导出条目 */
|
||||||
|
export function parseReferencesBulkContent(content) {
|
||||||
|
const raw = String(content || '').trim();
|
||||||
|
if (!raw) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = raw.replace(/^(\s*<[^>]+>\s*)*References(\s*<[^>]+>\s*)*\s*/i, '');
|
||||||
|
body = body.replace(/^References\s*\n+/i, '').trim();
|
||||||
|
if (!body) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (/<html[\s>]/i.test(body) && typeof DOMParser !== 'undefined') {
|
||||||
|
const doc = new DOMParser().parseFromString(body, 'text/html');
|
||||||
|
const bulkNode = doc.querySelector('#ref-bulk');
|
||||||
|
if (bulkNode) {
|
||||||
|
body = bulkNode.innerHTML || bulkNode.textContent || '';
|
||||||
|
} else if (doc.body) {
|
||||||
|
body = doc.body.innerHTML || doc.body.textContent || body;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return splitReferenceSegments(body)
|
||||||
|
.map(function (segment) {
|
||||||
|
return segmentToReferenceItem(segment);
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 预览整段参考文献可解析条数(粘贴弹窗用) */
|
||||||
|
export function countParsedReferences(content) {
|
||||||
|
return parseReferencesBulkContent(content).length;
|
||||||
|
}
|
||||||
|
|
||||||
export function parseReferencesEditableHtml(htmlText) {
|
export function parseReferencesEditableHtml(htmlText) {
|
||||||
const raw = String(htmlText || '');
|
const raw = String(htmlText || '');
|
||||||
if (!raw.trim()) {
|
if (!raw.trim()) {
|
||||||
@@ -191,21 +383,30 @@ export function parseReferencesEditableHtml(htmlText) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const doc = new DOMParser().parseFromString(raw, 'text/html');
|
const doc = new DOMParser().parseFromString(raw, 'text/html');
|
||||||
|
const bulkNode = doc.querySelector('#ref-bulk');
|
||||||
|
if (bulkNode) {
|
||||||
|
return parseReferencesBulkContent(bulkNode.innerHTML || bulkNode.textContent || '');
|
||||||
|
}
|
||||||
|
|
||||||
const nodes = doc.querySelectorAll('#ref-list .ref-item, #ref-list li[data-ref-index], ol.ref-list li');
|
const nodes = doc.querySelectorAll('#ref-list .ref-item, #ref-list li[data-ref-index], ol.ref-list li');
|
||||||
const items = [];
|
if (nodes.length) {
|
||||||
|
const items = [];
|
||||||
nodes.forEach(function (node) {
|
nodes.forEach(function (node) {
|
||||||
const html = (node.innerHTML || '').trim();
|
const html = (node.innerHTML || '').trim();
|
||||||
if (!html) {
|
if (!html) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
items.push({
|
items.push({
|
||||||
refer_type: String(node.getAttribute('data-ref-type') || 'journal').toLowerCase(),
|
refer_type: String(node.getAttribute('data-ref-type') || 'journal').toLowerCase(),
|
||||||
html: html
|
html: html
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
if (items.length) {
|
||||||
|
return items;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return items;
|
return parseReferencesBulkContent(raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function downloadReferencesEditableHtml(references, labels, fileName) {
|
export function downloadReferencesEditableHtml(references, labels, fileName) {
|
||||||
@@ -231,6 +432,137 @@ export function buildReferencesHtmlLabels(translate) {
|
|||||||
intro: t('commonTable.refHtmlIntro'),
|
intro: t('commonTable.refHtmlIntro'),
|
||||||
saveTip: t('commonTable.refHtmlSaveTip'),
|
saveTip: t('commonTable.refHtmlSaveTip'),
|
||||||
downloadEdited: t('commonTable.refHtmlDownloadEdited'),
|
downloadEdited: t('commonTable.refHtmlDownloadEdited'),
|
||||||
downloadFileName: t('commonTable.refHtmlDownloadFileName')
|
downloadFileName: t('commonTable.refHtmlDownloadFileName'),
|
||||||
|
bulkHint: t('commonTable.refHtmlBulkHint')
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildReferenceCopyItemHtml(ref) {
|
||||||
|
let html = referenceRecordToEditableHtml(ref);
|
||||||
|
if (!html) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
html = normalizeReferencePublicationDetails(html);
|
||||||
|
html = html.replace(/\s*Available at:\s*<br\s*\/?>/gi, ' Available at:<br>');
|
||||||
|
html = html.replace(/( Available at:<br\s*\/?>)([\s\S]*)$/i, function (_, prefix, linkPart) {
|
||||||
|
const trimmed = String(linkPart || '').trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return prefix;
|
||||||
|
}
|
||||||
|
if (/^<(a|span)\b/i.test(trimmed)) {
|
||||||
|
return prefix + trimmed;
|
||||||
|
}
|
||||||
|
return prefix + '<span style="color:#' + REF_COPY_DOI_COLOR + '">' + trimmed + '</span>';
|
||||||
|
});
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按 Word 导出格式生成可复制 HTML(编号、期刊斜体、DOI 蓝色、Available at 换行) */
|
||||||
|
export function buildReferencesCopyHtml(references) {
|
||||||
|
const list = (references || []).filter(Boolean);
|
||||||
|
return list
|
||||||
|
.map(function (ref, index) {
|
||||||
|
const html = buildReferenceCopyItemHtml(ref);
|
||||||
|
if (!html) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
'<p style="margin:0 0 8pt;line-height:1.5;font-family:' +
|
||||||
|
REF_COPY_FONT +
|
||||||
|
';font-size:12pt">' +
|
||||||
|
(index + 1) +
|
||||||
|
'. ' +
|
||||||
|
html +
|
||||||
|
'</p>'
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildReferencesCopyHtmlDocument(references) {
|
||||||
|
return (
|
||||||
|
'<!DOCTYPE html><html><head><meta charset="utf-8"></head><body style="font-family:' +
|
||||||
|
REF_COPY_FONT +
|
||||||
|
';font-size:12pt">' +
|
||||||
|
buildReferencesCopyHtml(references) +
|
||||||
|
'</body></html>'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 按 Word 导出格式生成可复制纯文本(编号 + 换行) */
|
||||||
|
export function buildReferencesCopyText(references) {
|
||||||
|
const list = (references || []).filter(Boolean);
|
||||||
|
return list
|
||||||
|
.map(function (ref, index) {
|
||||||
|
const html = buildReferenceCopyItemHtml(ref);
|
||||||
|
const text = htmlToPlainText(String(html).replace(/<br\s*\/?>/gi, '\n')).trim();
|
||||||
|
if (!text) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return index + 1 + '. ' + text;
|
||||||
|
})
|
||||||
|
.filter(Boolean)
|
||||||
|
.join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyTextToClipboard(text) {
|
||||||
|
const value = String(text || '').trim();
|
||||||
|
if (!value) {
|
||||||
|
return Promise.reject(new Error('EMPTY'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
||||||
|
return navigator.clipboard.writeText(value).catch(function () {
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.value = value;
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const ta = document.createElement('textarea');
|
||||||
|
ta.value = value;
|
||||||
|
document.body.appendChild(ta);
|
||||||
|
ta.select();
|
||||||
|
document.execCommand('copy');
|
||||||
|
document.body.removeChild(ta);
|
||||||
|
return Promise.resolve();
|
||||||
|
}
|
||||||
|
|
||||||
|
function copyRichTextToClipboard(plainText, htmlText) {
|
||||||
|
const plain = String(plainText || '').trim();
|
||||||
|
const html = String(htmlText || '').trim();
|
||||||
|
if (!plain) {
|
||||||
|
return Promise.reject(new Error('EMPTY'));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof navigator !== 'undefined' && navigator.clipboard && window.ClipboardItem && html) {
|
||||||
|
const htmlBlob = new Blob([html], { type: 'text/html' });
|
||||||
|
const plainBlob = new Blob([plain], { type: 'text/plain' });
|
||||||
|
return navigator.clipboard
|
||||||
|
.write([
|
||||||
|
new ClipboardItem({
|
||||||
|
'text/html': htmlBlob,
|
||||||
|
'text/plain': plainBlob
|
||||||
|
})
|
||||||
|
])
|
||||||
|
.catch(function () {
|
||||||
|
return copyTextToClipboard(plain);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return copyTextToClipboard(plain);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 复制参考文献到剪贴板(Word 导出同款格式,含斜体与蓝色 DOI) */
|
||||||
|
export function copyReferencesToClipboard(references) {
|
||||||
|
const text = buildReferencesCopyText(references);
|
||||||
|
const html = buildReferencesCopyHtmlDocument(references);
|
||||||
|
if (!text) {
|
||||||
|
return Promise.reject(new Error('EMPTY'));
|
||||||
|
}
|
||||||
|
return copyRichTextToClipboard(text, html);
|
||||||
|
}
|
||||||
|
|||||||
65
src/utils/parseBookReference.js
Normal file
65
src/utils/parseBookReference.js
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
/**
|
||||||
|
* 按书籍引用规则解析 Content:
|
||||||
|
* Author. Title. Publication Details. ISBN
|
||||||
|
* 标题在 Author 之后第一个句号(.)处结束;其后至 ISBN 前(去掉 Available at:)为 Publication Details
|
||||||
|
*/
|
||||||
|
function splitAtFirstPeriod(text) {
|
||||||
|
const raw = String(text || '').trim();
|
||||||
|
if (!raw) return { before: '', after: '' };
|
||||||
|
const match = raw.match(/\s*\.\s+/);
|
||||||
|
if (!match || match.index == null) {
|
||||||
|
return { before: raw, after: '' };
|
||||||
|
}
|
||||||
|
const before = raw.slice(0, match.index).trim();
|
||||||
|
const after = raw.slice(match.index + match[0].length).trim();
|
||||||
|
return { before, after };
|
||||||
|
}
|
||||||
|
|
||||||
|
function withAuthorPeriod(name) {
|
||||||
|
const t = String(name || '').trim();
|
||||||
|
if (!t) return '';
|
||||||
|
return t.endsWith('.') ? t : t + '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
function trimTitle(text) {
|
||||||
|
return String(text || '')
|
||||||
|
.trim()
|
||||||
|
.replace(/\s+\.\s*$/, '')
|
||||||
|
.replace(/\.\s*$/, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function parseBookReferenceContent(content) {
|
||||||
|
let text = String(content || '')
|
||||||
|
.replace(/\u00a0/g, ' ')
|
||||||
|
.replace(/\s+/g, ' ')
|
||||||
|
.trim();
|
||||||
|
if (!text) return null;
|
||||||
|
|
||||||
|
let isbn = '';
|
||||||
|
const isbnMatch = text.match(/ISBN:\s*([\d\-Xx]+)/i);
|
||||||
|
if (isbnMatch) {
|
||||||
|
isbn = isbnMatch[1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
text = text
|
||||||
|
.replace(/\s*\.?\s*Available\s+at:\s*ISBN:\s*[\d\-Xx]+.*$/i, '')
|
||||||
|
.replace(/\s*Available\s+at:\s*ISBN:\s*[\d\-Xx]+.*$/i, '')
|
||||||
|
.replace(/\s*Available\s+at:\s*[\d\-Xx\-]+.*$/i, '')
|
||||||
|
.replace(/\s*ISBN:\s*[\d\-Xx]+.*$/i, '')
|
||||||
|
.trim()
|
||||||
|
.replace(/\.\s*$/, '');
|
||||||
|
|
||||||
|
const authorSplit = splitAtFirstPeriod(text);
|
||||||
|
const author = withAuthorPeriod(authorSplit.before);
|
||||||
|
const afterAuthor = authorSplit.after;
|
||||||
|
|
||||||
|
const titleSplit = splitAtFirstPeriod(afterAuthor);
|
||||||
|
const title = trimTitle(titleSplit.before);
|
||||||
|
const dateno = trimTitle(titleSplit.after);
|
||||||
|
|
||||||
|
if (!author && !title && !dateno && !isbn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { author, title, dateno, isbn };
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -5,3 +5,16 @@ export function isZyModeEnabled(route) {
|
|||||||
}
|
}
|
||||||
return String(route.query.zy) === '1';
|
return String(route.query.zy) === '1';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isLocalDevHost() {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const host = window.location.hostname;
|
||||||
|
return host === 'localhost' || host === '127.0.0.1';
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 本地开发 + zy=1 时跳过 checkRefer,直接生成初稿 */
|
||||||
|
export function isZySkipCheckEnabled(route) {
|
||||||
|
return isZyModeEnabled(route) && isLocalDevHost();
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user