添加期刊引用分析

This commit is contained in:
2026-06-17 09:35:15 +08:00
parent ec4a59eedb
commit ea5695f913
36 changed files with 6129 additions and 103 deletions

View File

@@ -0,0 +1,377 @@
import { AlignmentType, ImageRun, Paragraph, TextRun } from 'docx';
import { htmlToPlainText, TABLE_FONT_SIZE, WORD_PARAGRAPH_SPACING } from '@/utils/exportTableWord';
import orcidIconUrl from '@/assets/img/orcid.png';
/** 作者区字体 Calibri 六号7.5pt */
const AUTHOR_FONT_NAME = 'Calibri';
/** 标题略大 */
const AUTHOR_TITLE_FONT_SIZE = 24;
/** 上标 rgb(0, 112, 192) */
const AUTHOR_SUPER_COLOR = '0070C0';
const ORCID_ICON_SIZE = 13;
let orcidImageCache = null;
export async function loadOrcidImageData() {
if (orcidImageCache) {
return orcidImageCache;
}
if (typeof fetch === 'undefined') {
return null;
}
const response = await fetch(orcidIconUrl);
const buffer = await response.arrayBuffer();
orcidImageCache = new Uint8Array(buffer);
return orcidImageCache;
}
export async function fetchManuscriptHeaderData(apiClient, articleId, pArticleId, resolvePArticleId) {
if (!apiClient) {
return null;
}
const resolvedId = await resolvePArticleId(apiClient, articleId, pArticleId);
if (!resolvedId) {
return null;
}
const idPayload = { p_article_id: resolvedId };
const results = await Promise.all([
apiClient.post('api/Production/getProductionDetail', idPayload).catch(function () {
return null;
}),
apiClient.post('api/Production/getAuthorlist', idPayload).catch(function () {
return null;
}),
apiClient.post('api/Production/getProductionPreview', idPayload).catch(function () {
return null;
})
]);
const detailRes = results[0];
const authorListRes = results[1];
const previewRes = results[2];
const production =
detailRes && detailRes.code == 0 && detailRes.data && detailRes.data.production
? detailRes.data.production
: null;
const authors =
authorListRes && authorListRes.code == 0 && authorListRes.data && authorListRes.data.authors
? authorListRes.data.authors
: [];
const previewAuthor =
previewRes && previewRes.code == 0 && previewRes.data && previewRes.data.author ? previewRes.data.author : null;
if (!production && !authors.length && !previewAuthor) {
return null;
}
return {
pArticleId: resolvedId,
production: production,
authors: authors,
previewAuthor: previewAuthor
};
}
function createAuthorTextRun(text, options) {
const opts = options || {};
const runOptions = {
text: text || '',
font: AUTHOR_FONT_NAME,
size: TABLE_FONT_SIZE
};
if (opts.bold) {
runOptions.bold = true;
}
if (opts.italics) {
runOptions.italics = true;
}
if (opts.superScript) {
runOptions.superScript = true;
}
if (opts.color) {
runOptions.color = opts.color;
}
if (opts.size) {
runOptions.size = opts.size;
}
return new TextRun(runOptions);
}
function createOrcidImageRun(orcidImageData) {
if (!orcidImageData) {
return createAuthorTextRun('');
}
return new ImageRun({
type: 'png',
data: orcidImageData,
transformation: {
width: ORCID_ICON_SIZE,
height: ORCID_ICON_SIZE
}
});
}
/** 与排版预览一致:<q> 转上标,作者之间加空格 */
function preparePreviewAuthorHtml(html) {
let text = String(html || '');
text = text.replace(/<q>/gi, '<sup>');
text = text.replace(/<\/q>/gi, '</sup>');
text = text.replace(/<\/sup>,/gi, '</sup>, ');
text = text.replace(/<span[^>]*margin-left:\s*8px[^>]*>\s*<\/span>/gi, ' ');
text = text.replace(/,\s*$/, '');
return text.trim();
}
function decodeAuthorHtmlEntities(text) {
return String(text || '')
.replace(/&nbsp;/g, ' ')
.replace(/&amp;/g, '&')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"');
}
function htmlToAuthorLineRuns(html, orcidImageData, isAuthorNameLine) {
const runs = [];
const raw = String(html || '');
if (!raw) {
return runs;
}
if (typeof document === 'undefined') {
runs.push(
createAuthorTextRun(htmlToPlainText(raw), {
bold: !!isAuthorNameLine
})
);
return runs;
}
const root = document.createElement('div');
root.innerHTML = raw;
function appendRun(text, style) {
if (!text) {
return;
}
runs.push(
createAuthorTextRun(text, {
bold: style.bold,
italics: style.italic,
superScript: style.super,
color: style.color
})
);
}
function walk(node, style) {
if (node.nodeType === Node.TEXT_NODE) {
appendRun(decodeAuthorHtmlEntities(node.textContent).replace(/\u00a0/g, ' '), style);
return;
}
if (node.nodeType !== Node.ELEMENT_NODE) {
return;
}
const tag = node.tagName.toLowerCase();
if (tag === 'br') {
return;
}
if (tag === 'img') {
runs.push(createOrcidImageRun(orcidImageData));
return;
}
if (tag === 'span' && !node.textContent.trim()) {
return;
}
const nextStyle = {
bold: style.bold,
italic: style.italic,
super: style.super,
color: style.color
};
if (tag === 'b' || tag === 'strong') {
nextStyle.bold = true;
}
if (tag === 'i' || tag === 'em') {
nextStyle.italic = true;
}
if (tag === 'sup') {
nextStyle.super = true;
nextStyle.bold = false;
nextStyle.color = AUTHOR_SUPER_COLOR;
}
Array.from(node.childNodes).forEach(function (child) {
walk(child, nextStyle);
});
}
walk(root, {
bold: !!isAuthorNameLine,
italic: false,
super: false,
color: undefined
});
if (!runs.length) {
runs.push(
createAuthorTextRun(htmlToPlainText(raw), {
bold: !!isAuthorNameLine
})
);
}
return runs;
}
function buildAuthorHtmlParagraphs(authorHtml, orcidImageData) {
const prepared = preparePreviewAuthorHtml(authorHtml);
const segments = prepared.split(/<br\s*\/?>/gi).filter(function (segment) {
return htmlToPlainText(segment).trim();
});
if (!segments.length) {
return [];
}
return segments.map(function (segment, index) {
const isAuthorNameLine = index === 0;
const runs = htmlToAuthorLineRuns(segment, orcidImageData, isAuthorNameLine);
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: WORD_PARAGRAPH_SPACING,
children: runs.length ? runs : [createAuthorTextRun('')]
});
});
}
function buildAuthorListFallbackParagraph(authors, orcidImageData) {
const runs = [];
const list = (authors || []).filter(Boolean);
list.forEach(function (author, index) {
if (index > 0) {
runs.push(createAuthorTextRun(', '));
}
const name =
author.author_name ||
String((author.first_name || '') + ' ' + (author.last_name || '')).trim();
runs.push(createAuthorTextRun(name, { bold: true }));
const marks = [];
if (author.is_first == 1 || author.is_first == '1') {
marks.push('#');
}
if (author.is_report == 1 || author.is_report == '1') {
marks.push('*');
}
if (marks.length) {
runs.push(
createAuthorTextRun(marks.join(''), {
superScript: true,
color: AUTHOR_SUPER_COLOR
})
);
}
if (author.orcid && String(author.orcid).trim()) {
runs.push(createOrcidImageRun(orcidImageData));
}
});
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: WORD_PARAGRAPH_SPACING,
children: runs.length ? runs : [createAuthorTextRun('')]
});
}
function createAffiliationParagraph(index, addressText) {
const text = String(addressText || '').trim();
if (!text) {
return null;
}
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: WORD_PARAGRAPH_SPACING,
children: [
createAuthorTextRun(String(index + 1), {
superScript: true,
color: AUTHOR_SUPER_COLOR
}),
createAuthorTextRun(' ' + text)
]
});
}
function createAuthorTitleParagraph(title) {
const text = String(title || '').trim();
if (!text) {
return null;
}
return new Paragraph({
alignment: AlignmentType.JUSTIFIED,
spacing: WORD_PARAGRAPH_SPACING,
children: [
createAuthorTextRun(text, {
bold: true,
size: AUTHOR_TITLE_FONT_SIZE
})
]
});
}
export function buildAuthorHeaderSectionChildren(headerData, orcidImageData) {
if (!headerData) {
return [];
}
const children = [];
const production = headerData.production || {};
const previewAuthor = headerData.previewAuthor || {};
const titleParagraph = createAuthorTitleParagraph(htmlToPlainText(production.title || ''));
if (titleParagraph) {
children.push(titleParagraph);
}
if (previewAuthor && previewAuthor.author) {
buildAuthorHtmlParagraphs(previewAuthor.author, orcidImageData).forEach(function (paragraph) {
children.push(paragraph);
});
} else if (headerData.authors && headerData.authors.length) {
children.push(buildAuthorListFallbackParagraph(headerData.authors, orcidImageData));
}
const addressList = (previewAuthor && previewAuthor.addressList) || [];
addressList.forEach(function (address, index) {
const paragraph = createAffiliationParagraph(index, htmlToPlainText(address));
if (paragraph) {
children.push(paragraph);
}
});
if (children.length) {
children.push(
new Paragraph({
spacing: WORD_PARAGRAPH_SPACING,
children: [createAuthorTextRun('')]
})
);
}
return children;
}