相关性代码调整
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: 'http://www.tougao.com/', //测试本地 记得切换
|
||||
// baseURL: 'http://192.168.110.110/tougao/public/index.php/',
|
||||
// baseURL: '/api', //本地
|
||||
baseURL: '/', //正式
|
||||
baseURL: '/api', //本地
|
||||
// baseURL: '/', //正式
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import mammoth from "mammoth";
|
||||
import { MathfieldElement } from 'mathlive';
|
||||
import 'mathlive/dist/mathlive-static.css';
|
||||
import 'mathlive/dist/mathlive-fonts.css';
|
||||
import { importWordDocumentWithMath as parseWordDocumentWithMath, parseHtmlToLatex as convertHtmlToLatex } from '@/utils/wordMathImport';
|
||||
import { importWordDocumentWithMath as parseWordDocumentWithMath, parseHtmlToLatex as convertHtmlToLatex, postProcessImportedWordHtml, parseImportedHtmlToContentRows, mergeAdjacentBlueTags, normalizeSpacesAroundBlueTags } from '@/utils/wordMathImport';
|
||||
import api from '../../api/index.js';
|
||||
import Common from '@/components/common/common'
|
||||
import Tiff from 'tiff.js';
|
||||
@@ -1518,6 +1518,9 @@ str = str.replace(regex, function (match, content, offset, fullString) {
|
||||
if (type == 'table' && tag == 'img' && (attrName === "src" || attrName === "width" || attrName === "height")) {
|
||||
return attrMatch;
|
||||
}
|
||||
if (tag === 'img' && (attrName === 'src' || attrName === 'width' || attrName === 'height' || attrName === 'alt')) {
|
||||
return attrMatch;
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
@@ -1527,7 +1530,7 @@ str = str.replace(regex, function (match, content, offset, fullString) {
|
||||
if (type == 'table') {
|
||||
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|img|myfigure|mytable|myh3))[^>]+>/g, ''); // 删除不需要的标签
|
||||
} else {
|
||||
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|myfigure|mytable|myh3))[^>]+>/g, ''); // 删除不需要的标签
|
||||
inputHtml = inputHtml.replace(/<(?!\/?(strong|em|sub|sup|b|i|blue|wmath|myfigure|mytable|myh3|img))[^>]+>/g, ''); // 删除不需要的标签
|
||||
}
|
||||
|
||||
|
||||
@@ -1543,8 +1546,8 @@ str = str.replace(regex, function (match, content, offset, fullString) {
|
||||
|
||||
},
|
||||
|
||||
importWordDocumentWithMath(file) {
|
||||
return parseWordDocumentWithMath(file);
|
||||
importWordDocumentWithMath(file, options) {
|
||||
return parseWordDocumentWithMath(file, options);
|
||||
},
|
||||
|
||||
parseHtmlToLatex(html) {
|
||||
@@ -1552,42 +1555,35 @@ str = str.replace(regex, function (match, content, offset, fullString) {
|
||||
},
|
||||
|
||||
cleanAndParseWordContent(content) {
|
||||
// 1️⃣ 解析成 <p> 段落数组
|
||||
let tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content; // 解析 HTML 内容
|
||||
let paragraphs = tempDiv.querySelectorAll("p"); // 选取所有 <p> 作为数据项
|
||||
const rows = parseImportedHtmlToContentRows(content);
|
||||
const parsedData = [];
|
||||
|
||||
// 2️⃣ 将 <p> 内容转换为数组,并处理内容
|
||||
let parsedData = Array.from(paragraphs).map(p => {
|
||||
let text = p.innerHTML.trim(); // 获取内容,去除两端空格
|
||||
text = replaceNegativeSign(text);
|
||||
rows.forEach((raw) => {
|
||||
const rawStr = String(raw || '');
|
||||
if (rawStr && (/wordTableHtml/i.test(rawStr) || /<table[\s>]/i.test(rawStr))) {
|
||||
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)
|
||||
// 3️⃣ **正确移除 <o:p>(Word 复制的无效标签)**
|
||||
text = text.replace(/<\/?o:p[^>]*>/g, "");
|
||||
|
||||
// 4️⃣ **移除所有 style="..."**
|
||||
text = text.replace(/\s*style="[^"]*"/gi, "");
|
||||
|
||||
// 5️⃣ **修正标签替换**
|
||||
text = text.replace(/<strong>/gi, "<b>").replace(/<\/strong>/gi, "</b>");
|
||||
text = text.replace(/<em>/gi, "<i>").replace(/<\/em>/gi, "</i>");
|
||||
|
||||
// 6️⃣ **移除空的 span、b、i 标签**
|
||||
text = text.replace(/<span>\s*<\/span>/gi, "");
|
||||
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;
|
||||
let text = replaceNegativeSign(rawStr.trim());
|
||||
text = this.transformHtmlString(text);
|
||||
text = text.replace(/<\/?o:p[^>]*>/g, '');
|
||||
text = text.replace(/\s*style="[^"]*"/gi, '');
|
||||
text = text.replace(/<strong>/gi, '<b>').replace(/<\/strong>/gi, '</b>');
|
||||
text = text.replace(/<em>/gi, '<i>').replace(/<\/em>/gi, '</i>');
|
||||
text = text.replace(/<span>\s*<\/span>/gi, '');
|
||||
text = text.replace(/<b>\s*<\/b>/gi, '');
|
||||
text = text.replace(/<i>\s*<\/i>/gi, '');
|
||||
text = text.replace(/<[^\/>]+>\s*<\/[^>]+>/gi, (match) => (match.trim() === '' ? '' : match));
|
||||
text = mergeAdjacentBlueTags(text);
|
||||
text = normalizeSpacesAroundBlueTags(text);
|
||||
parsedData.push(text.trim() === '' ? '' : text);
|
||||
});
|
||||
|
||||
|
||||
return parsedData;
|
||||
},
|
||||
|
||||
@@ -2865,12 +2861,12 @@ str = str.replace(regex, function (match, content, offset, fullString) {
|
||||
background: 'rgba(0, 0, 0, 0.45)'
|
||||
})
|
||||
: null;
|
||||
parseWordDocumentWithMath(file)
|
||||
parseWordDocumentWithMath(file, { textOnly: true })
|
||||
.then((html) => {
|
||||
if (!html) {
|
||||
throw new Error('empty content');
|
||||
}
|
||||
ed.insertContent(html);
|
||||
ed.setContent(html);
|
||||
const body = ed.getBody();
|
||||
body.querySelectorAll('wmath').forEach((el) => {
|
||||
let latex = (el.getAttribute('data-latex') || '').trim();
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
//记得切换
|
||||
|
||||
//正式
|
||||
const mediaUrl = '/public/';
|
||||
const baseUrl = '/';
|
||||
// const mediaUrl = '/public/';
|
||||
// const baseUrl = '/';
|
||||
|
||||
//正式环境
|
||||
|
||||
// const mediaUrl = 'https://submission.tmrjournals.com/public/';
|
||||
// // const mediaUrl = 'http://zmzm.tougao.dev.com/public/';
|
||||
// const baseUrl = '/api'
|
||||
const mediaUrl = 'https://submission.tmrjournals.com/public/';
|
||||
// const mediaUrl = 'http://zmzm.tougao.dev.com/public/';
|
||||
const baseUrl = '/api'
|
||||
|
||||
//测试环境
|
||||
|
||||
|
||||
@@ -382,7 +382,9 @@ const en = {
|
||||
unsubscribeSwitchOff: 'Subscribed',
|
||||
unsubscribeMissingId: 'Missing expert ID, unable to switch unsubscribe status',
|
||||
unsubscribeUpdateSuccess: 'Unsubscribe status updated',
|
||||
unsubscribeUpdateFailed: 'Failed to update unsubscribe status'
|
||||
unsubscribeUpdateFailed: 'Failed to update unsubscribe status',
|
||||
lastContactTime: 'Last contact time: {time}',
|
||||
lastContactTimeEmpty: 'No contact record'
|
||||
},
|
||||
countryManagement: {
|
||||
title: 'Country Management',
|
||||
@@ -1146,10 +1148,12 @@ const en = {
|
||||
AnnotationList: 'Annotation List',
|
||||
Annotations: 'Comments',
|
||||
exportWord: 'Generate Word file',
|
||||
exportWordShort: 'Word',
|
||||
exportManuscriptEmpty: 'No content to export.',
|
||||
exportManuscriptSuccess: 'Word downloaded.',
|
||||
exportManuscriptFail: 'Failed to export Word.',
|
||||
citeRelevanceDetect: 'Download relevance HTML',
|
||||
citeRelevanceDetectShort: 'Relevance',
|
||||
citeRelevanceTitle: 'Citation relevance check',
|
||||
citeRelevanceTitleProgress: 'Citation relevance ({current}/{total})',
|
||||
citeRelevanceEmpty: 'No citations to review.',
|
||||
@@ -1182,8 +1186,34 @@ const en = {
|
||||
citeRelevanceCopyGroup: 'Copy paragraph',
|
||||
citeRelevanceDownloadHtml: 'Download HTML',
|
||||
citeRelevanceDownloadHtmlSuccess: 'HTML downloaded',
|
||||
refHtmlCopy: 'Copy references',
|
||||
refHtmlCopyShort: 'Copy',
|
||||
refHtmlCopySuccess: 'References copied (numbering, italic journal, blue DOI, Available at line break)',
|
||||
parseBookReference: 'Parse Book',
|
||||
parseBookReferenceSuccess: 'Book fields parsed successfully',
|
||||
parseBookReferenceEmpty: 'Could not parse book from Content. Expected: Author. Title. Publication Details. ISBN',
|
||||
parseBookReferenceContentEmpty: 'Content cannot be empty',
|
||||
refHtmlDownload: 'Download references HTML',
|
||||
refHtmlDownloadSuccess: 'References HTML downloaded',
|
||||
refHtmlDownloadShort: 'Ref HTML',
|
||||
refHtmlDownloadSuccess: 'References HTML downloaded. After editing, click "Upload references" then "Generate Word file".',
|
||||
refHtmlUpload: 'Upload references',
|
||||
refHtmlUploadShort: 'Upload',
|
||||
refHtmlUploadSuccess: '{n} reference(s) loaded. Click "Generate Word file".',
|
||||
refHtmlUploadConfirm: 'Confirm load',
|
||||
refHtmlUploadLoadedTip: '{n} reference(s) loaded — click "Generate Word file" to export',
|
||||
exportWordWithUploadedRefs: 'Generate Word with {n} uploaded reference(s)',
|
||||
exportWordWithUploadedRefsSuccess: 'Word generated with {n} uploaded reference(s)',
|
||||
refHtmlPaste: 'Paste references',
|
||||
refHtmlPasteTitle: 'Paste full references',
|
||||
refHtmlPasteTip: 'Paste the complete references below — no need to edit one by one. Separate entries with blank lines or numbering (1. 2. …); HTML tags such as <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',
|
||||
refHtmlToWordSuccess: 'Word exported with edited references',
|
||||
refHtmlToWordFail: 'Failed to convert references HTML to Word',
|
||||
@@ -1192,8 +1222,8 @@ const en = {
|
||||
refHtmlParseEmpty: 'No reference entries found in HTML. Please use the HTML downloaded from this system.',
|
||||
refHtmlPageTitle: 'References editor',
|
||||
refHtmlReferencesTitle: 'References',
|
||||
refHtmlIntro: 'Edit each reference directly, or paste AI output (supports <i> italics and other HTML). When done, click "Download edited HTML", then use "References HTML to Word" on the typesetting page.',
|
||||
refHtmlSaveTip: 'Tip: save the HTML after editing, then import it on the typesetting page to export Word.',
|
||||
refHtmlIntro: 'Download to edit all references in one block, or paste AI output (supports <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 the full block, or copy the content and use "Paste references" on the typesetting page.',
|
||||
refHtmlDownloadEdited: 'Download edited HTML',
|
||||
refHtmlDownloadFileName: 'references-edited.html',
|
||||
exportImg: 'Export PNG',
|
||||
@@ -1264,6 +1294,8 @@ const en = {
|
||||
Row: "Row",
|
||||
addRow: "Add Row",
|
||||
Uncheck: 'Uncheck the paragraph',
|
||||
selectAll: 'Select All',
|
||||
selectAllShort: 'All',
|
||||
ManuscirptAIProofreading: 'Manuscript AI Proofreading',
|
||||
AIProofreading: 'AI Proofreading',
|
||||
Association: 'Association',
|
||||
|
||||
@@ -371,7 +371,9 @@ const zh = {
|
||||
unsubscribeSwitchOff: '已订阅',
|
||||
unsubscribeMissingId: '缺少专家 ID,无法切换退订状态',
|
||||
unsubscribeUpdateSuccess: '退订状态更新成功',
|
||||
unsubscribeUpdateFailed: '退订状态更新失败'
|
||||
unsubscribeUpdateFailed: '退订状态更新失败',
|
||||
lastContactTime: '最近一次联系时间:{time}',
|
||||
lastContactTimeEmpty: '暂无联系记录'
|
||||
},
|
||||
countryManagement: {
|
||||
title: '国家信息维护',
|
||||
@@ -1132,10 +1134,12 @@ const zh = {
|
||||
AnnotationList: '批注列表',
|
||||
Annotations: '批注',
|
||||
exportWord: '生成 Word 文件',
|
||||
exportWordShort: 'Word',
|
||||
exportManuscriptEmpty: '没有可导出的内容。',
|
||||
exportManuscriptSuccess: 'Word 已下载。',
|
||||
exportManuscriptFail: 'Word 导出失败。',
|
||||
citeRelevanceDetect: '下载相关性 HTML',
|
||||
citeRelevanceDetectShort: '相关性',
|
||||
citeRelevanceTitle: '引文相关性核查',
|
||||
citeRelevanceTitleProgress: '引文相关性核查 ({current}/{total})',
|
||||
citeRelevanceEmpty: '当前没有可核查的引文。',
|
||||
@@ -1168,8 +1172,34 @@ const zh = {
|
||||
citeRelevanceCopyGroup: '复制本段',
|
||||
citeRelevanceDownloadHtml: '下载 HTML',
|
||||
citeRelevanceDownloadHtmlSuccess: 'HTML 已下载',
|
||||
refHtmlCopy: '复制参考文献',
|
||||
refHtmlCopyShort: '复制',
|
||||
refHtmlCopySuccess: '参考文献已复制(含编号、期刊斜体、蓝色 DOI、Available at 换行)',
|
||||
parseBookReference: '解析书籍',
|
||||
parseBookReferenceSuccess: '书籍字段已按规则解析',
|
||||
parseBookReferenceEmpty: '无法从 Content 中解析书籍信息,请检查格式(Author. Title. Publication Details. ISBN)',
|
||||
parseBookReferenceContentEmpty: 'Content 不能为空',
|
||||
refHtmlDownload: '下载参考文献 HTML',
|
||||
refHtmlDownloadSuccess: '参考文献 HTML 已下载',
|
||||
refHtmlDownloadShort: 'Ref HTML',
|
||||
refHtmlDownloadSuccess: '参考文献 HTML 已下载,改完后请点「上传参考文献」再点「生成 Word 文件」',
|
||||
refHtmlUpload: '上传参考文献',
|
||||
refHtmlUploadShort: '上传',
|
||||
refHtmlUploadSuccess: '已加载 {n} 条参考文献,请点击「生成 Word 文件」',
|
||||
refHtmlUploadConfirm: '确认加载',
|
||||
refHtmlUploadLoadedTip: '已加载 {n} 条参考文献,点击「生成 Word 文件」导出',
|
||||
exportWordWithUploadedRefs: '将使用已上传的 {n} 条参考文献生成 Word',
|
||||
exportWordWithUploadedRefsSuccess: 'Word 已生成(含上传的 {n} 条参考文献)',
|
||||
refHtmlPaste: '粘贴参考文献',
|
||||
refHtmlPasteTitle: '粘贴完整参考文献',
|
||||
refHtmlPasteTip: '将整段参考文献粘贴到下方即可,无需逐条修改。支持 1. 2. 编号分隔,或条目之间空一行;也支持 <i> 斜体等 HTML 标签。',
|
||||
refHtmlPastePlaceholder: '在此粘贴完整参考文献内容…',
|
||||
refHtmlPasteParsed: '已识别 {n} 条参考文献',
|
||||
refHtmlPasteParseEmpty: '暂未识别到参考文献,请使用「复制参考文献」的格式(1. 2. 编号 + Available at 换行)',
|
||||
refHtmlPasteExport: '导出 Word',
|
||||
refHtmlPasteCancel: '取消',
|
||||
refHtmlImportHtml: '从 HTML 文件导入',
|
||||
refHtmlImportSuccess: '已导入到编辑框,确认后点击「导出 Word」',
|
||||
refHtmlBulkHint: '以下为整段参考文献,可整体替换或粘贴 AI 返回的完整内容;条目之间用空行或 1. 2. 编号分隔。',
|
||||
refHtmlToWord: '参考文献 HTML 转 Word',
|
||||
refHtmlToWordSuccess: 'Word 已导出(含编辑后的参考文献)',
|
||||
refHtmlToWordFail: '参考文献 HTML 转 Word 失败',
|
||||
@@ -1178,8 +1208,8 @@ const zh = {
|
||||
refHtmlParseEmpty: 'HTML 中未找到参考文献条目,请使用本系统下载的参考文献 HTML',
|
||||
refHtmlPageTitle: '参考文献编辑',
|
||||
refHtmlReferencesTitle: 'References',
|
||||
refHtmlIntro: '可直接编辑每条文献,或粘贴 AI 返回的内容(支持 <i> 斜体等 HTML 标签)。编辑完成后点击「下载编辑后的 HTML」,再在排版页使用「参考文献 HTML 转 Word」。',
|
||||
refHtmlSaveTip: '提示:编辑后请点击上方按钮保存 HTML;也可在排版页导入该文件导出 Word。',
|
||||
refHtmlIntro: '下载后可在一个大文本框里整体编辑全部参考文献,或粘贴 AI 返回的完整内容(支持 <i> 斜体等 HTML 标签)。编辑完成后保存 HTML,或在排版页使用「粘贴参考文献」导出 Word。',
|
||||
refHtmlSaveTip: '提示:整段编辑后点击上方按钮保存 HTML;也可复制内容后在排版页「粘贴参考文献」导出 Word。',
|
||||
refHtmlDownloadEdited: '下载编辑后的 HTML',
|
||||
refHtmlDownloadFileName: 'references-edited.html',
|
||||
exportImg: '导出 图片',
|
||||
@@ -1249,6 +1279,8 @@ const zh = {
|
||||
Row:"空行",
|
||||
addRow:"新增空行",
|
||||
Uncheck:'取消勾选段落',
|
||||
selectAll: '全选',
|
||||
selectAllShort: '全选',
|
||||
ManuscirptAIProofreading:'稿件AI校对',
|
||||
AIProofreading:'AI校对',
|
||||
Association:'关联',
|
||||
|
||||
@@ -416,7 +416,8 @@
|
||||
type="content"
|
||||
@openLatexEditor="openLatexEditor"
|
||||
v-if="addContentVisible"
|
||||
:enable-import-word-math="true"
|
||||
:enable-import-word-math="zyModeEnabled"
|
||||
:article-id="articleId"
|
||||
ref="addContent"
|
||||
style="margin-left: -115px"
|
||||
></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 bottomTinymce from '@/components/page/components/Tinymce';
|
||||
import catalogue from '@/components/page/components/table/catalogue.vue';
|
||||
import { isZyModeEnabled } from '@/utils/zyMode';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -578,6 +580,9 @@ export default {
|
||||
catalogue
|
||||
},
|
||||
computed: {
|
||||
zyModeEnabled() {
|
||||
return isZyModeEnabled(this.$route);
|
||||
},
|
||||
combinedValue() {
|
||||
// 将两个值组合成一个新的值,可以是字符串、数组、对象等
|
||||
// return `${this.isFirstComponentLoaded}-${this.isWordComponentLoaded}`;
|
||||
@@ -726,17 +731,7 @@ export default {
|
||||
|
||||
this.saveContent(content, this.currentContent.am_id);
|
||||
} else if (type == 'addcontent') {
|
||||
var hasTable = /<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);
|
||||
|
||||
|
||||
this.saveContentList(list, this.currentId);
|
||||
} else if (type == 'table') {
|
||||
|
||||
@@ -726,17 +726,7 @@ export default {
|
||||
|
||||
this.saveContent(content, this.currentContent.am_id);
|
||||
} 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);
|
||||
|
||||
|
||||
this.saveContentList(list, this.currentId);
|
||||
} else if (type == 'table') {
|
||||
|
||||
@@ -1182,7 +1182,7 @@ import {
|
||||
getAuthorDisplayName,
|
||||
getAuthorAffiliationPreview
|
||||
} from '@/utils/productionSubmissionImport';
|
||||
import { isZyModeEnabled } from '@/utils/zyMode';
|
||||
import { isZyModeEnabled, isZySkipCheckEnabled } from '@/utils/zyMode';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
@@ -2892,39 +2892,19 @@ export default {
|
||||
|
||||
// 6----创建文章
|
||||
EstaBlish() {
|
||||
if (isZySkipCheckEnabled(this.$route)) {
|
||||
this.doTypeSettingNewDirect();
|
||||
return;
|
||||
}
|
||||
this.$api
|
||||
.post('api/Production/checkRefer', {
|
||||
p_article_id: this.p_article_id
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code == 0) {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: 'Loading...',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
this.$api
|
||||
.post('api/Production/doTypeSettingNew', {
|
||||
article_id: this.detailMes.article_id
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.getWorldPdf();
|
||||
this.$message.success('Successfully generated manuscript!');
|
||||
loading.close();
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
loading.close();
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
this.$message.error(err);
|
||||
loading.close();
|
||||
});
|
||||
this.doTypeSettingNewDirect();
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
return;
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
@@ -2932,6 +2912,32 @@ export default {
|
||||
});
|
||||
},
|
||||
|
||||
doTypeSettingNewDirect() {
|
||||
const loading = this.$loading({
|
||||
lock: true,
|
||||
text: 'Loading...',
|
||||
spinner: 'el-icon-loading',
|
||||
background: 'rgba(0, 0, 0, 0.7)'
|
||||
});
|
||||
this.$api
|
||||
.post('api/Production/doTypeSettingNew', {
|
||||
article_id: this.detailMes.article_id
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code == 0) {
|
||||
this.getWorldPdf();
|
||||
this.$message.success('Successfully generated manuscript!');
|
||||
} else {
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
loading.close();
|
||||
})
|
||||
.catch((err) => {
|
||||
this.$message.error(err);
|
||||
loading.close();
|
||||
});
|
||||
},
|
||||
|
||||
// 6----校对文章
|
||||
htmlContet() {
|
||||
this.$api
|
||||
|
||||
@@ -11,6 +11,7 @@ import { Document, Packer, PageOrientation, Paragraph, TextRun } from 'docx'; //
|
||||
import html2canvas from 'html2canvas';
|
||||
import { extractHexImagesFromRTF, hexToBlob } from '@/utils/rtfParser';
|
||||
import { tableStyle } from '@/utils/tinymceStyles';
|
||||
import { normalizeEditorPasteHtml, isStructuredDataTable, normalizeSpacesAroundBlueTags } from '@/utils/wordMathImport';
|
||||
export default {
|
||||
name: 'tinymce',
|
||||
components: {},
|
||||
@@ -63,6 +64,11 @@ export default {
|
||||
},
|
||||
articleId: {
|
||||
default: ''
|
||||
},
|
||||
/** Word 批量导入:保留 base64 图片,不自动上传 */
|
||||
keepInlineImages: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -698,6 +704,8 @@ export default {
|
||||
plugins: 'texttransform noneditable table image', // 启用 forecolor 和 code 插件
|
||||
// plugins: 'forecolor code paste table image mathType searchreplace raw', // 启用 forecolor 和 code 插件
|
||||
end_container_on_empty_block: true,
|
||||
automatic_uploads: !_this.keepInlineImages,
|
||||
paste_data_images: true,
|
||||
content_css: 'default', // 加载 TinyMCE 默认样式表
|
||||
mathjax: {
|
||||
// 配置 MathJax 用于渲染数学公式
|
||||
@@ -707,6 +715,19 @@ export default {
|
||||
// automatic_uploads: false,
|
||||
images_upload_handler: function (blobInfo, progress) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const inlineUri = blobInfo.blobUri ? blobInfo.blobUri() : '';
|
||||
if (_this.keepInlineImages && inlineUri && /^data:/i.test(inlineUri)) {
|
||||
resolve(inlineUri);
|
||||
return;
|
||||
}
|
||||
if (!_this.articleId) {
|
||||
if (inlineUri) {
|
||||
resolve(inlineUri);
|
||||
return;
|
||||
}
|
||||
reject('Upload Error: Please select an article');
|
||||
return;
|
||||
}
|
||||
const xhr = new XMLHttpRequest();
|
||||
const formData = new FormData();
|
||||
const file = blobInfo.blob();
|
||||
@@ -926,23 +947,28 @@ export default {
|
||||
e.content = e.content.replace(/<i>/g, '<em>').replace(/<\/i>/g, '</em>');
|
||||
});
|
||||
},
|
||||
paste_preprocess: function (plugin, args) {
|
||||
paste_preprocess: function (editor, args) {
|
||||
let imgIdx = 0;
|
||||
|
||||
const silentPlaceholder = 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7';
|
||||
|
||||
let content = args.content.replace(/(<img[^>]*?)src="file:\/\/\/[^" ]*"/gi, (match, p1) => {
|
||||
// 保留 data-idx, Word 里的尺寸
|
||||
let content = String(args.content || '').replace(/(<img[^>]*?)src="file:\/\/\/[^" ]*"/gi, (match, p1) => {
|
||||
return `${p1}src="${silentPlaceholder}" class="word-img-placeholder" data-idx="${imgIdx++}"`;
|
||||
});
|
||||
let tempDiv = document.createElement('div');
|
||||
tempDiv.innerHTML = content;
|
||||
|
||||
if (tempDiv.querySelector('table')) {
|
||||
if (_this.type == 'table') {
|
||||
|
||||
_this.$commonJS.parseTableToArray(content, (tableList) => {
|
||||
var contentHtml = `
|
||||
const tableEl = tempDiv.querySelector('table');
|
||||
const isRealDataTable =
|
||||
tableEl &&
|
||||
_this.type === 'table' &&
|
||||
_this.isAutomaticUpdate &&
|
||||
isStructuredDataTable(tableEl);
|
||||
|
||||
if (isRealDataTable) {
|
||||
_this.$commonJS.parseTableToArray(content, (tableList) => {
|
||||
if (!tableList || !tableList.length) return;
|
||||
const contentHtml = `
|
||||
<div class="thumbnailTableBox wordTableHtml table_Box table_Box3333" style="">
|
||||
<table border="1" style="width: auto; border-collapse: collapse; text-align: center;">
|
||||
${tableList
|
||||
@@ -965,12 +991,10 @@ export default {
|
||||
</table>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = contentHtml;
|
||||
args.content = container.innerHTML; // 更新处理后的内容
|
||||
});
|
||||
}
|
||||
const container = document.createElement('div');
|
||||
container.innerHTML = contentHtml;
|
||||
args.content = normalizeEditorPasteHtml(container.innerHTML);
|
||||
});
|
||||
} else {
|
||||
const plainText = (tempDiv.textContent || tempDiv.innerText || '').trim();
|
||||
const builtPlain = _this.buildWmathHtmlFromLatexText(plainText);
|
||||
@@ -987,18 +1011,18 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
if (args.event) {
|
||||
args.event.preventDefault();
|
||||
args.event.stopPropagation();
|
||||
}
|
||||
|
||||
if (_this.isAutomaticUpdate) {
|
||||
args.content = _this.$commonJS.transformHtmlString(content); // 更新处理后的内容
|
||||
} else {
|
||||
args.content = content;
|
||||
if (!isRealDataTable) {
|
||||
const normalized = normalizeEditorPasteHtml(content);
|
||||
if (_this.isAutomaticUpdate) {
|
||||
args.content = _this.$commonJS.transformHtmlString(normalized);
|
||||
} else {
|
||||
args.content = normalized;
|
||||
}
|
||||
}
|
||||
setTimeout(() => {
|
||||
window.renderMathJax(_this.tinymceId);
|
||||
if (typeof window.renderMathJax === 'function') {
|
||||
window.renderMathJax(_this.tinymceId);
|
||||
}
|
||||
}, 10);
|
||||
},
|
||||
clear_custom_action: (editor, vm) => {
|
||||
@@ -1101,7 +1125,11 @@ export default {
|
||||
content = content.replace(/<span[^>]*>/g, '').replace(/<\/span>/g, ''); // 去除span标签
|
||||
content = content.replace(/<strong>/g, '<b>').replace(/<\/strong>/g, '</b>');
|
||||
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);
|
||||
},
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
ref="tinymceChild1"
|
||||
:wordStyle="wordStyle"
|
||||
:isAutomaticUpdate="isAutomaticUpdate"
|
||||
:article-id="articleId"
|
||||
:keep-inline-images="enableImportWordMath"
|
||||
@getContent="getContent"
|
||||
@openLatexEditor="openLatexEditor"
|
||||
@updateChange="updateChange"
|
||||
@@ -48,6 +50,9 @@ export default {
|
||||
enableImportWordMath: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
articleId: {
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
components: {
|
||||
@@ -62,9 +67,9 @@ export default {
|
||||
if (!this.isAutomaticUpdate) {
|
||||
groups.push('LateX');
|
||||
}
|
||||
// if (this.enableImportWordMath) {
|
||||
// groups.push('importWordMath');
|
||||
// }
|
||||
if (this.enableImportWordMath) {
|
||||
groups.push('importWordMath');
|
||||
}
|
||||
groups.push(
|
||||
'myuppercase myuppercasea Line MoreSymbols',
|
||||
'subscript superscript',
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
: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="handleSelectAll" v-if="!isPreview && zyModeEnabled" plain type="info" size="mini">
|
||||
<i class="el-icon-circle-check"></i> {{ $t('commonTable.selectAllShort') }}
|
||||
</el-button>
|
||||
<div
|
||||
@click="handleClickAI()"
|
||||
v-if="isEditComment && !isPreview && activeName == 'proofreading'"
|
||||
@@ -76,39 +79,78 @@
|
||||
</li>
|
||||
<li
|
||||
v-if="zyModeEnabled"
|
||||
@click="handleExportManuscriptWord"
|
||||
class="base-font-size base-bg-imp base-padding-all"
|
||||
:style="{ '--f-s': '12px', '--f-c': '#333' }"
|
||||
@click="handleDownloadReferencesHtml"
|
||||
class="zy-toolbar-btn"
|
||||
>
|
||||
<i
|
||||
class="el-icon-download base-margin"
|
||||
:style="{ '--m-r': '2px' }"
|
||||
v-if="!exportingManuscriptWord"
|
||||
></i>
|
||||
<i
|
||||
class="el-icon-loading base-margin"
|
||||
:style="{ '--m-r': '2px' }"
|
||||
v-else
|
||||
></i>
|
||||
{{ $t('commonTable.exportWord') }}
|
||||
<el-tooltip :content="$t('commonTable.refHtmlDownload')" placement="bottom">
|
||||
<span class="zy-toolbar-btn-inner">
|
||||
<i
|
||||
class="el-icon-document"
|
||||
v-if="!referencesHtmlLoading"
|
||||
></i>
|
||||
<i class="el-icon-loading" v-else></i>
|
||||
<span class="zy-toolbar-label">{{ $t('commonTable.refHtmlDownloadShort') }}</span>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</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
|
||||
v-if="zyModeEnabled"
|
||||
@click="handleOpenCitationRelevance"
|
||||
class="base-font-size base-bg-imp base-padding-all"
|
||||
:style="{ '--f-s': '12px', '--f-c': '#333' }"
|
||||
class="zy-toolbar-btn"
|
||||
>
|
||||
<i
|
||||
class="el-icon-connection base-margin"
|
||||
:style="{ '--m-r': '2px' }"
|
||||
v-if="!citationRelevanceLoading"
|
||||
></i>
|
||||
<i
|
||||
class="el-icon-loading base-margin"
|
||||
:style="{ '--m-r': '2px' }"
|
||||
v-else
|
||||
></i>
|
||||
{{ $t('commonTable.citeRelevanceDetect') }}
|
||||
<el-tooltip :content="$t('commonTable.citeRelevanceDetect')" placement="bottom">
|
||||
<span class="zy-toolbar-btn-inner">
|
||||
<i
|
||||
class="el-icon-connection"
|
||||
v-if="!citationRelevanceLoading"
|
||||
></i>
|
||||
<i class="el-icon-loading" v-else></i>
|
||||
<span class="zy-toolbar-label">{{ $t('commonTable.citeRelevanceDetectShort') }}</span>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1008,6 +1050,43 @@
|
||||
</el-button>
|
||||
</span>
|
||||
</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
|
||||
v-for="(m, i) in markers"
|
||||
@@ -1101,6 +1180,15 @@ import { debounce, throttle } from '@/common/js/debounce';
|
||||
import { tableStyle, commonWordStyle } from '@/utils/tinymceStyles';
|
||||
import LatexDataPanel from './LatexDataPanel.vue';
|
||||
import { downloadManuscriptWord, fetchManuscriptReferenceList } from '@/utils/exportManuscriptWord';
|
||||
import {
|
||||
buildReferencesCopyText,
|
||||
buildReferencesHtmlLabels,
|
||||
copyReferencesToClipboard,
|
||||
countParsedReferences,
|
||||
downloadReferencesEditableHtml,
|
||||
parseReferencesBulkContent,
|
||||
parseReferencesEditableHtml
|
||||
} from '@/utils/manuscriptReferenceHtml';
|
||||
import {
|
||||
buildCitationReviewQueue,
|
||||
buildCitationReviewHtmlLabels,
|
||||
@@ -1232,6 +1320,14 @@ export default {
|
||||
scrollPosition: 0,
|
||||
wordList: [],
|
||||
exportingManuscriptWord: false,
|
||||
referencesCopyLoading: false,
|
||||
referencesHtmlLoading: false,
|
||||
referencesUploadLoading: false,
|
||||
uploadedReferenceHtmlItems: null,
|
||||
uploadedReferencesCount: 0,
|
||||
referencesPasteVisible: false,
|
||||
referencesPasteText: '',
|
||||
referencesPasteCount: 0,
|
||||
citationRelevanceLoading: false,
|
||||
manuscriptReferences: [],
|
||||
proofreadingList: [],
|
||||
@@ -1361,6 +1457,15 @@ export default {
|
||||
zyModeEnabled() {
|
||||
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() {
|
||||
const order = [2, 1, 3];
|
||||
const rank = { 2: 0, 1: 1, 3: 2 };
|
||||
@@ -1496,6 +1601,164 @@ export default {
|
||||
this.editors = {};
|
||||
},
|
||||
methods: {
|
||||
async loadManuscriptReferences(forceReload) {
|
||||
if (!forceReload && this.manuscriptReferences && this.manuscriptReferences.length) {
|
||||
return this.manuscriptReferences;
|
||||
}
|
||||
if (!this.$api) {
|
||||
throw new Error('NO_API');
|
||||
}
|
||||
const references = await fetchManuscriptReferenceList(this.$api, this.articleId, this.pArticleId);
|
||||
this.manuscriptReferences = references || [];
|
||||
return this.manuscriptReferences;
|
||||
},
|
||||
async handleCopyReferences() {
|
||||
if (this.referencesCopyLoading) {
|
||||
return;
|
||||
}
|
||||
if (!this.$api) {
|
||||
this.$message.error(this.$t('commonTable.refHtmlLoadFail'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.referencesCopyLoading = true;
|
||||
try {
|
||||
const references = await this.loadManuscriptReferences(false);
|
||||
if (!references.length) {
|
||||
this.$message.warning(this.$t('commonTable.refHtmlEmpty'));
|
||||
return;
|
||||
}
|
||||
await copyReferencesToClipboard(references);
|
||||
this.$message.success(this.$t('commonTable.refHtmlCopySuccess'));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (err && err.message === 'EMPTY') {
|
||||
this.$message.warning(this.$t('commonTable.refHtmlEmpty'));
|
||||
} else {
|
||||
this.$message.error(this.$t('commonTable.refHtmlLoadFail'));
|
||||
}
|
||||
} finally {
|
||||
this.referencesCopyLoading = false;
|
||||
}
|
||||
},
|
||||
async handleDownloadReferencesHtml() {
|
||||
if (this.referencesHtmlLoading) {
|
||||
return;
|
||||
}
|
||||
if (!this.$api) {
|
||||
this.$message.error(this.$t('commonTable.refHtmlLoadFail'));
|
||||
return;
|
||||
}
|
||||
|
||||
this.referencesHtmlLoading = true;
|
||||
try {
|
||||
const references = await this.loadManuscriptReferences(false);
|
||||
if (!references.length) {
|
||||
this.$message.warning(this.$t('commonTable.refHtmlEmpty'));
|
||||
return;
|
||||
}
|
||||
|
||||
const labels = buildReferencesHtmlLabels(this.$t.bind(this));
|
||||
const fileName = 'references-editor' + (this.articleId ? '-' + this.articleId : '') + '.html';
|
||||
downloadReferencesEditableHtml(references, labels, fileName);
|
||||
this.$message.success(this.$t('commonTable.refHtmlDownloadSuccess'));
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.$message.error(this.$t('commonTable.refHtmlLoadFail'));
|
||||
} finally {
|
||||
this.referencesHtmlLoading = false;
|
||||
}
|
||||
},
|
||||
triggerReferencesUpload() {
|
||||
const input = this.$refs.referencesHtmlFileInput;
|
||||
if (input) {
|
||||
input.value = '';
|
||||
input.click();
|
||||
}
|
||||
},
|
||||
triggerReferencesHtmlFileImport() {
|
||||
this.triggerReferencesUpload();
|
||||
},
|
||||
parseUploadedReferenceContent(text, fileName) {
|
||||
const raw = String(text || '');
|
||||
const isPlainText = /\.txt$/i.test(String(fileName || ''));
|
||||
if (isPlainText) {
|
||||
return parseReferencesBulkContent(raw);
|
||||
}
|
||||
return parseReferencesEditableHtml(raw);
|
||||
},
|
||||
applyUploadedReferences(items) {
|
||||
if (!items || !items.length) {
|
||||
this.$message.warning(this.$t('commonTable.refHtmlParseEmpty'));
|
||||
return false;
|
||||
}
|
||||
this.uploadedReferenceHtmlItems = items;
|
||||
this.uploadedReferencesCount = items.length;
|
||||
this.$message.success(
|
||||
this.$t('commonTable.refHtmlUploadSuccess', { n: items.length })
|
||||
);
|
||||
return true;
|
||||
},
|
||||
handleReferencesFileUpload(event) {
|
||||
const input = event && event.target;
|
||||
const file = input && input.files && input.files[0];
|
||||
if (!file) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.referencesUploadLoading = true;
|
||||
const reader = new FileReader();
|
||||
reader.onload = () => {
|
||||
try {
|
||||
const items = this.parseUploadedReferenceContent(reader.result, file.name);
|
||||
this.applyUploadedReferences(items);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.$message.error(this.$t('commonTable.refHtmlToWordFail'));
|
||||
} finally {
|
||||
this.referencesUploadLoading = false;
|
||||
if (input) {
|
||||
input.value = '';
|
||||
}
|
||||
}
|
||||
};
|
||||
reader.onerror = () => {
|
||||
this.referencesUploadLoading = false;
|
||||
if (input) {
|
||||
input.value = '';
|
||||
}
|
||||
this.$message.error(this.$t('commonTable.refHtmlToWordFail'));
|
||||
};
|
||||
reader.readAsText(file, 'UTF-8');
|
||||
},
|
||||
async openReferencesPasteDialog() {
|
||||
if (this.referencesUploadLoading) {
|
||||
return;
|
||||
}
|
||||
if (!this.$api) {
|
||||
this.$message.error(this.$t('commonTable.refHtmlLoadFail'));
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const references = await this.loadManuscriptReferences(false);
|
||||
this.referencesPasteText = references.length ? buildReferencesCopyText(references) : '';
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
this.referencesPasteText = '';
|
||||
}
|
||||
this.updateReferencesPastePreview();
|
||||
this.referencesPasteVisible = true;
|
||||
},
|
||||
updateReferencesPastePreview() {
|
||||
this.referencesPasteCount = countParsedReferences(this.referencesPasteText);
|
||||
},
|
||||
confirmReferencesUpload() {
|
||||
const items = parseReferencesBulkContent(this.referencesPasteText);
|
||||
if (this.applyUploadedReferences(items)) {
|
||||
this.referencesPasteVisible = false;
|
||||
}
|
||||
},
|
||||
async handleExportManuscriptWord() {
|
||||
if (this.exportingManuscriptWord) {
|
||||
return;
|
||||
@@ -1506,19 +1769,32 @@ export default {
|
||||
}
|
||||
this.exportingManuscriptWord = true;
|
||||
try {
|
||||
const hasUploaded =
|
||||
this.uploadedReferenceHtmlItems && this.uploadedReferenceHtmlItems.length;
|
||||
await downloadManuscriptWord(this.wordList, this.mediaUrl, 'manuscript', {
|
||||
fetchReferences: true,
|
||||
fetchReferences: !hasUploaded,
|
||||
referenceHtmlItems: hasUploaded ? this.uploadedReferenceHtmlItems : null,
|
||||
apiClient: this.$api,
|
||||
articleId: this.articleId,
|
||||
pArticleId: this.pArticleId
|
||||
});
|
||||
this.$message.success(this.$t('commonTable.exportManuscriptSuccess') || 'Word downloaded.');
|
||||
this.$message.success(
|
||||
hasUploaded
|
||||
? this.$t('commonTable.exportWordWithUploadedRefsSuccess', {
|
||||
n: this.uploadedReferencesCount
|
||||
})
|
||||
: this.$t('commonTable.exportManuscriptSuccess') || 'Word downloaded.'
|
||||
);
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (err && err.message === 'NO_CONTENT') {
|
||||
this.$message.warning(this.$t('commonTable.exportManuscriptEmpty') || 'No content to export.');
|
||||
} else {
|
||||
this.$message.error(this.$t('commonTable.exportManuscriptFail') || 'Failed to export Word.');
|
||||
const detail =
|
||||
err && err.message
|
||||
? err.message
|
||||
: this.$t('commonTable.exportManuscriptFail') || 'Failed to export Word.';
|
||||
this.$message.error(detail);
|
||||
}
|
||||
} finally {
|
||||
this.exportingManuscriptWord = false;
|
||||
@@ -1539,11 +1815,7 @@ export default {
|
||||
|
||||
this.citationRelevanceLoading = true;
|
||||
try {
|
||||
let references = this.manuscriptReferences;
|
||||
if (!references || !references.length) {
|
||||
references = await fetchManuscriptReferenceList(this.$api, this.articleId, this.pArticleId);
|
||||
this.manuscriptReferences = references || [];
|
||||
}
|
||||
const references = await this.loadManuscriptReferences(false);
|
||||
|
||||
const items = buildCitationReviewQueue(this.wordList, this.manuscriptReferences);
|
||||
if (!items.length) {
|
||||
@@ -2607,6 +2879,14 @@ export default {
|
||||
this.selectedIds = [];
|
||||
this.$forceUpdate();
|
||||
},
|
||||
handleSelectAll() {
|
||||
this.currentId = null;
|
||||
this.currentData = {};
|
||||
this.selectedIds = (this.wordList || [])
|
||||
.map((item) => (item && item.am_id != null ? item.am_id : null))
|
||||
.filter((id) => id != null);
|
||||
this.$forceUpdate();
|
||||
},
|
||||
onEdit() {
|
||||
this.$emit('onEdit', this.currentId);
|
||||
},
|
||||
@@ -4378,6 +4658,52 @@ export default {
|
||||
border-radius: 0;
|
||||
height: auto;
|
||||
}
|
||||
.HTitleBox li.zy-toolbar-btn {
|
||||
padding: 4px 10px;
|
||||
margin: 0 2px;
|
||||
font-size: 12px !important;
|
||||
font-weight: normal !important;
|
||||
min-width: auto;
|
||||
height: auto;
|
||||
color: #409eff;
|
||||
background: #fff;
|
||||
border: 1px solid #409eff;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
.HTitleBox li.zy-toolbar-btn:hover {
|
||||
background: #ecf5ff;
|
||||
}
|
||||
.HTitleBox li.zy-toolbar-btn.zy-toolbar-btn--ready {
|
||||
border-color: #67c23a;
|
||||
color: #67c23a;
|
||||
}
|
||||
.HTitleBox li.zy-toolbar-btn.zy-toolbar-btn--ready:hover {
|
||||
background: #f0f9eb;
|
||||
}
|
||||
.zy-toolbar-btn-inner {
|
||||
display: inline-flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
line-height: 1.2;
|
||||
pointer-events: none;
|
||||
}
|
||||
.zy-toolbar-btn-inner i {
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
.zy-toolbar-label {
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
font-weight: normal;
|
||||
}
|
||||
.zy-toolbar-badge {
|
||||
font-style: normal;
|
||||
color: inherit;
|
||||
font-weight: bold;
|
||||
}
|
||||
.operateBox {
|
||||
width: auto;
|
||||
display: flex;
|
||||
|
||||
@@ -556,6 +556,16 @@
|
||||
>We have detected updates to the reference content. You need to click the "Automatic parsing" button to
|
||||
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>
|
||||
</el-form-item>
|
||||
|
||||
@@ -898,6 +908,8 @@
|
||||
<script>
|
||||
import VueUeditorWrap from 'vue-ueditor-wrap'; // ES6 Module
|
||||
import ReferenceSearchLinks from '@/components/page/components/ReferenceSearchLinks.vue';
|
||||
import { isZyModeEnabled } from '@/utils/zyMode';
|
||||
import { parseBookReferenceContent } from '@/utils/parseBookReference';
|
||||
|
||||
/** 单条引用 records[].status:0 待检测 2 已完成 3 检测失败 */
|
||||
const REF_RELEVANCE_RECORD_STATUS_PENDING = 0;
|
||||
@@ -1072,6 +1084,9 @@ export default {
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
zyModeEnabled() {
|
||||
return isZyModeEnabled(this.$route);
|
||||
},
|
||||
showRefRelevanceToolbar() {
|
||||
return this.role === 'editor' && this.showB_step === 2 && !this.refRelevanceStatusLoading;
|
||||
},
|
||||
@@ -1542,6 +1557,27 @@ export default {
|
||||
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) {
|
||||
return this.$api
|
||||
|
||||
@@ -71,7 +71,14 @@
|
||||
<span class="value time">{{ scope.row.ctime_text ? scope.row.ctime_text : $t('expertDatabase.emptyMark') }}</span>
|
||||
</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>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@@ -289,6 +296,20 @@ export default {
|
||||
getUnsubscribeValue(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) {
|
||||
const expertId = this.getExpertId(row);
|
||||
if (!expertId) {
|
||||
@@ -432,6 +453,7 @@ export default {
|
||||
color: #ce4f15;
|
||||
font-size: 12px;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
cursor: default;
|
||||
}
|
||||
.info-row {
|
||||
margin-bottom: 4px;
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData" border stripe class="table" ref="multipleTable" @sort-change="changeSort"
|
||||
header-cell-class-name="table-header">
|
||||
<el-table :data="tableData" border stripe class="table" ref="multipleTable" v-loading="listLoading"
|
||||
@sort-change="changeSort" header-cell-class-name="table-header">
|
||||
<el-table-column label="Basic Information">
|
||||
<template slot-scope="scope">
|
||||
<p class="tab_tie_col">
|
||||
@@ -147,8 +147,16 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="pagination">
|
||||
<el-pagination background layout="total, prev, pager, next" :current-page="query.pageIndex"
|
||||
:page-size="query.pageSize" :total="Total" @current-change="handlePageChange"></el-pagination>
|
||||
<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>
|
||||
|
||||
@@ -266,6 +274,8 @@ import bus from '../common/bus'
|
||||
order_remark: 0
|
||||
},
|
||||
Total: 0,
|
||||
listLoading: false,
|
||||
paginationSizeChanging: false,
|
||||
df_jour: [],
|
||||
df_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() {
|
||||
this.$api
|
||||
.post('api/User/getYboardlist', this.query)
|
||||
const params = this.buildListParams();
|
||||
this.listLoading = true;
|
||||
return this.$api
|
||||
.post('api/User/getYboardlist', params)
|
||||
.then(res => {
|
||||
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++) {
|
||||
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 {
|
||||
this.tableData = [];
|
||||
this.Total = 0;
|
||||
this.$message.error(res.msg);
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
this.tableData = [];
|
||||
this.Total = 0;
|
||||
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) {
|
||||
this.$set(this.query, 'pageIndex', val);
|
||||
if (this.paginationSizeChanging) {
|
||||
return;
|
||||
}
|
||||
this.query.pageIndex = val;
|
||||
this.getDate();
|
||||
},
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { AlignmentType, ExternalHyperlink, LineRuleType, Packer, Paragraph, TextRun } from 'docx';
|
||||
import { saveAs } from 'file-saver';
|
||||
import {
|
||||
appendFigureTitleParagraphs,
|
||||
appendTableBlockEmptyLines,
|
||||
createTableParagraph,
|
||||
createWordDocument,
|
||||
@@ -93,22 +94,7 @@ function buildFigureWordChildren(item, mediaUrl) {
|
||||
);
|
||||
|
||||
if (prepared.title) {
|
||||
splitHtmlSegments(prepared.title).forEach((segment) => {
|
||||
children.push(
|
||||
createTableParagraph(
|
||||
[
|
||||
new TextRun({
|
||||
text: htmlToPlainText(segment),
|
||||
bold: true,
|
||||
color: TITLE_COLOR,
|
||||
font: FONT_NAME,
|
||||
size: TABLE_FONT_SIZE
|
||||
})
|
||||
],
|
||||
AlignmentType.JUSTIFIED
|
||||
)
|
||||
);
|
||||
});
|
||||
appendFigureTitleParagraphs(children, prepared.title);
|
||||
}
|
||||
|
||||
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
|
||||
} from 'docx';
|
||||
import { saveAs } from 'file-saver';
|
||||
import JSZip from 'jszip';
|
||||
import { TableUtils } from '@/common/js/TableUtils';
|
||||
import { isMathFormulaTableRecord } from '@/utils/mathFormulaModule';
|
||||
|
||||
@@ -28,6 +29,10 @@ const FONT_NAME = 'Charis SIL';
|
||||
/** 六号 = 7.5pt */
|
||||
const TABLE_FONT_SIZE = 15;
|
||||
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) {
|
||||
return Math.round((cm * 1440) / 2.54);
|
||||
@@ -73,7 +78,8 @@ const WORD_DOCUMENT_STYLES = {
|
||||
},
|
||||
run: {
|
||||
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,
|
||||
run: {
|
||||
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,
|
||||
size: TITLE_FONT_SIZE,
|
||||
bold: true,
|
||||
color: TITLE_COLOR
|
||||
color: TITLE_COLOR,
|
||||
kern: FONT_KERN_MIN_1PT
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -126,8 +134,7 @@ function createWordParagraph(children, options) {
|
||||
|
||||
if (opts.title) {
|
||||
paragraphOptions.alignment = AlignmentType.CENTER;
|
||||
paragraphOptions.style = 'Heading1';
|
||||
paragraphOptions.outlineLevel = 0;
|
||||
paragraphOptions.style = 'Normal';
|
||||
} else {
|
||||
paragraphOptions.alignment = alignment;
|
||||
paragraphOptions.style = 'Normal';
|
||||
@@ -143,9 +150,134 @@ function appendTableBlockEmptyLines(children) {
|
||||
}
|
||||
}
|
||||
|
||||
/** 表格标题段落:居中,大纲 1 级,固定行距 10pt,段前段后 0,无缩进 */
|
||||
/** 表格标题段落:居中,固定行距 10pt,段前段后 0,无缩进(非大纲一级) */
|
||||
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) {
|
||||
@@ -201,6 +333,42 @@ function isBlueElement(node) {
|
||||
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) {
|
||||
const { bold = false } = options || {};
|
||||
const runs = [];
|
||||
@@ -212,18 +380,42 @@ function htmlToTextRuns(html, options) {
|
||||
text: '',
|
||||
font: FONT_NAME,
|
||||
size: TABLE_FONT_SIZE,
|
||||
bold
|
||||
bold,
|
||||
kern: FONT_KERN_MIN_1PT
|
||||
})
|
||||
];
|
||||
}
|
||||
|
||||
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 [
|
||||
new TextRun({
|
||||
text: htmlToPlainText(raw),
|
||||
font: FONT_NAME,
|
||||
size: TABLE_FONT_SIZE,
|
||||
bold
|
||||
bold,
|
||||
kern: FONT_KERN_MIN_1PT
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -244,14 +436,19 @@ function htmlToTextRuns(html, options) {
|
||||
italics: style.italic,
|
||||
superScript: style.sup,
|
||||
subScript: style.sub,
|
||||
color: style.blue ? BLUE_COLOR : undefined
|
||||
color: style.blue ? BLUE_COLOR : undefined,
|
||||
kern: FONT_KERN_MIN_1PT
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
function walk(node, style) {
|
||||
if (node.nodeType === Node.TEXT_NODE) {
|
||||
appendRun(decodeHtmlEntities(node.textContent).replace(/\u00a0/g, ' '), style);
|
||||
appendRunsForCitationText(
|
||||
decodeHtmlEntities(node.textContent).replace(/\u00a0/g, ' '),
|
||||
style,
|
||||
appendRun
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (node.nodeType !== Node.ELEMENT_NODE) {
|
||||
@@ -304,7 +501,8 @@ function htmlToTextRuns(html, options) {
|
||||
text: htmlToPlainText(raw),
|
||||
font: FONT_NAME,
|
||||
size: TABLE_FONT_SIZE,
|
||||
bold
|
||||
bold,
|
||||
kern: FONT_KERN_MIN_1PT
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -360,7 +558,8 @@ function htmlToHeaderTextRuns(html) {
|
||||
text: formatHeaderWordSpacing(html),
|
||||
font: FONT_NAME,
|
||||
size: TABLE_FONT_SIZE,
|
||||
bold: true
|
||||
bold: true,
|
||||
kern: FONT_KERN_MIN_1PT
|
||||
})
|
||||
];
|
||||
}
|
||||
@@ -525,7 +724,8 @@ function buildTableWordChildren(processedItem, options) {
|
||||
bold: true,
|
||||
color: TITLE_COLOR,
|
||||
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) {
|
||||
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`);
|
||||
}
|
||||
|
||||
@@ -620,19 +821,26 @@ export async function downloadAllTablesWord(items, fileName) {
|
||||
}
|
||||
|
||||
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`);
|
||||
}
|
||||
|
||||
export {
|
||||
appendFigureTitleParagraphs,
|
||||
appendTableBlockEmptyLines,
|
||||
buildTableWordChildren,
|
||||
createFigureTitleParagraph,
|
||||
createTableParagraph,
|
||||
createWordDocument,
|
||||
ensureFigureTitleTrailingPeriod,
|
||||
FONT_KERN_MIN_1PT,
|
||||
FONT_KERN_MIN_XIAO_ER,
|
||||
FONT_NAME,
|
||||
htmlToPlainText,
|
||||
htmlToTextRuns,
|
||||
PAGE_MARGINS,
|
||||
patchBodyTableCaptionCenter,
|
||||
splitHtmlSegments,
|
||||
TABLE_FONT_SIZE,
|
||||
TITLE_COLOR,
|
||||
|
||||
@@ -74,6 +74,11 @@ export function getDualColumnWidthPt() {
|
||||
return Math.round(col1WidthTwips / 20);
|
||||
}
|
||||
|
||||
/** 版心宽度(pt),用于 H1 蓝色形状横跨整页 */
|
||||
export function getTextAreaWidthPt() {
|
||||
return Math.round(getTextAreaWidthTwips() / 20);
|
||||
}
|
||||
|
||||
export function createWordSectionProperties(columnCount, isFirstSection) {
|
||||
const properties = {
|
||||
page: {
|
||||
@@ -100,3 +105,52 @@ export function createWordSectionProperties(columnCount, isFirstSection) {
|
||||
|
||||
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 { normalizeReferencePublicationDetails } from '@/utils/exportManuscriptWord';
|
||||
|
||||
const REF_COPY_DOI_COLOR = '0082AA';
|
||||
const REF_COPY_FONT = 'Charis SIL, Georgia, Times New Roman, serif';
|
||||
|
||||
function escapeHtml(text) {
|
||||
return String(text || '')
|
||||
@@ -67,7 +71,7 @@ export function referenceRecordToEditableHtml(ref) {
|
||||
let html = stripAvailableAtSuffix(ref.refer_frag);
|
||||
const link = normalizeReferenceLink(ref.doilink || getReferenceIsbnValue(ref));
|
||||
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;
|
||||
}
|
||||
@@ -86,7 +90,7 @@ export function referenceRecordToEditableHtml(ref) {
|
||||
}
|
||||
const isbnValue = getReferenceIsbnValue(ref);
|
||||
if (isbnValue) {
|
||||
parts.push('Available at:<br>ISBN: ' + escapeHtml(isbnValue));
|
||||
parts.push(' Available at:<br>ISBN: ' + escapeHtml(isbnValue));
|
||||
}
|
||||
return parts.join('');
|
||||
}
|
||||
@@ -95,7 +99,7 @@ export function referenceRecordToEditableHtml(ref) {
|
||||
let html = stripAvailableAtSuffix(ref.refer_frag);
|
||||
const link = normalizeReferenceLink(ref.doilink);
|
||||
if (link) {
|
||||
html += '<br>Available at:<br>' + escapeHtml(ref.doilink || link);
|
||||
html += '<br> Available at:<br>' + escapeHtml(ref.doilink || link);
|
||||
}
|
||||
return html;
|
||||
}
|
||||
@@ -114,7 +118,7 @@ export function referenceRecordToEditableHtml(ref) {
|
||||
}
|
||||
const doiLink = normalizeReferenceLink(ref.doilink);
|
||||
if (doiLink) {
|
||||
parts.push('Available at:<br>' + escapeHtml(ref.doilink || doiLink));
|
||||
parts.push(' Available at:<br>' + escapeHtml(ref.doilink || doiLink));
|
||||
}
|
||||
|
||||
return parts.join('');
|
||||
@@ -128,18 +132,8 @@ export function buildReferencesEditableHtml(references, labels) {
|
||||
const saveTip = L.saveTip || '';
|
||||
const downloadBtn = L.downloadEdited || 'Download edited HTML';
|
||||
const refTitle = L.referencesTitle || 'References';
|
||||
|
||||
let listHtml = '';
|
||||
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>';
|
||||
});
|
||||
const bulkHint = L.bulkHint || '';
|
||||
const bulkContent = buildReferencesBulkHtml(list);
|
||||
|
||||
return (
|
||||
'<!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}' +
|
||||
'.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.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-list{margin:0;padding-left:1.4em;list-style:decimal}' +
|
||||
'.ref-item{margin-bottom:10px;padding:4px 0;outline:none;min-height:1.4em}' +
|
||||
'.ref-item:focus{background:#fafcff;box-shadow:inset 0 0 0 1px #c6e2ff;border-radius:2px}' +
|
||||
'.ref-item i,.ref-item em{font-style:italic}' +
|
||||
'.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-bulk:focus{border-color:#409eff;box-shadow:0 0 0 2px rgba(64,158,255,.15)}' +
|
||||
'.ref-bulk i,.ref-bulk em{font-style:italic}' +
|
||||
'.bulk-hint{margin:0 0 10px;color:#909399;font-size:12px}' +
|
||||
'</style></head><body><div class="page">' +
|
||||
'<div class="header"><h1>' + escapeHtml(refTitle) + '</h1>' +
|
||||
'<div class="intro">' + escapeHtml(intro) + '</div>' +
|
||||
@@ -167,9 +160,11 @@ export function buildReferencesEditableHtml(references, labels) {
|
||||
'</div>' +
|
||||
'<div class="intro" style="font-size:12px;color:#909399">' + escapeHtml(saveTip) + '</div>' +
|
||||
'</div>' +
|
||||
'<div class="ref-panel"><ol class="ref-list" id="ref-list">' +
|
||||
listHtml +
|
||||
'</ol></div></div>' +
|
||||
'<div class="ref-panel">' +
|
||||
'<p class="bulk-hint">' + escapeHtml(bulkHint) + '</p>' +
|
||||
'<div id="ref-bulk" class="ref-bulk" contenteditable="true" spellcheck="false">' +
|
||||
bulkContent +
|
||||
'</div></div></div>' +
|
||||
'<script>(function(){' +
|
||||
'function serializePage(){var clone=document.documentElement.cloneNode(true);' +
|
||||
'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) {
|
||||
const raw = String(htmlText || '');
|
||||
if (!raw.trim()) {
|
||||
@@ -191,21 +383,30 @@ export function parseReferencesEditableHtml(htmlText) {
|
||||
}
|
||||
|
||||
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 items = [];
|
||||
|
||||
nodes.forEach(function (node) {
|
||||
const html = (node.innerHTML || '').trim();
|
||||
if (!html) {
|
||||
return;
|
||||
}
|
||||
items.push({
|
||||
refer_type: String(node.getAttribute('data-ref-type') || 'journal').toLowerCase(),
|
||||
html: html
|
||||
if (nodes.length) {
|
||||
const items = [];
|
||||
nodes.forEach(function (node) {
|
||||
const html = (node.innerHTML || '').trim();
|
||||
if (!html) {
|
||||
return;
|
||||
}
|
||||
items.push({
|
||||
refer_type: String(node.getAttribute('data-ref-type') || 'journal').toLowerCase(),
|
||||
html: html
|
||||
});
|
||||
});
|
||||
});
|
||||
if (items.length) {
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
return items;
|
||||
return parseReferencesBulkContent(raw);
|
||||
}
|
||||
|
||||
export function downloadReferencesEditableHtml(references, labels, fileName) {
|
||||
@@ -231,6 +432,137 @@ export function buildReferencesHtmlLabels(translate) {
|
||||
intro: t('commonTable.refHtmlIntro'),
|
||||
saveTip: t('commonTable.refHtmlSaveTip'),
|
||||
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';
|
||||
}
|
||||
|
||||
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