相关性代码调整

This commit is contained in:
2026-06-24 16:52:24 +08:00
parent 5a1bbb0894
commit 5260ca8ea5
29 changed files with 6330 additions and 568 deletions

View File

@@ -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);
}