486 lines
16 KiB
JavaScript
486 lines
16 KiB
JavaScript
import { htmlToPlainText } from '@/utils/manuscriptCitationRelevance';
|
||
import { parseReferencesBulkContent } from '@/utils/manuscriptReferenceHtml';
|
||
import { extractReferencesSectionFromWordHtml, fetchWordReferencesSection } from '@/utils/manuscriptWordReferences';
|
||
import { importWordDocumentWithMath } from '@/utils/wordMathImport';
|
||
import { applyDealContentToFields, parseReferenceFieldsFromHtml, normalizeImportedReferenceFields, normalizeImportedReferenceText, finalizeReferenceFields } from '@/utils/parseReferenceFields';
|
||
|
||
function mapUploadedItemsToRows(items) {
|
||
return (items || []).map(function (item, index) {
|
||
const referType = String(item.refer_type || 'journal').toLowerCase();
|
||
const fields = parseReferenceFieldsFromHtml(item.html, referType);
|
||
return {
|
||
index: index + 1,
|
||
html: item.html || '',
|
||
refer_type: fields ? fields.refer_type : referType,
|
||
fields: fields
|
||
};
|
||
});
|
||
}
|
||
|
||
/** 解析本地 Word 文件中的 References 条目 */
|
||
export async function parseLocalWordReferencesFile(file) {
|
||
if (!file) {
|
||
return { found: false, rows: [], count: 0 };
|
||
}
|
||
const html = await importWordDocumentWithMath(file, { keepReferences: true });
|
||
const extracted = extractReferencesSectionFromWordHtml(html);
|
||
if (!extracted.found) {
|
||
return { found: false, rows: [], count: 0 };
|
||
}
|
||
const items = parseReferencesBulkContent(extracted.html);
|
||
const rows = mapUploadedItemsToRows(items);
|
||
return {
|
||
found: rows.length > 0,
|
||
rows: rows,
|
||
count: rows.length
|
||
};
|
||
}
|
||
|
||
/** 从 gridData 稿件路径下载并解析 References */
|
||
export async function parseManuscriptWordReferences(apiClient, options) {
|
||
const extracted = await fetchWordReferencesSection(apiClient, options || {});
|
||
if (!extracted.found) {
|
||
return { found: false, rows: [], count: 0 };
|
||
}
|
||
const items = parseReferencesBulkContent(extracted.html);
|
||
const rows = mapUploadedItemsToRows(items);
|
||
return {
|
||
found: rows.length > 0,
|
||
rows: rows,
|
||
count: rows.length
|
||
};
|
||
}
|
||
|
||
function previewExistingReference(row) {
|
||
if (!row) return '';
|
||
if (row.refer_frag) {
|
||
return htmlToPlainText(row.refer_frag).replace(/\s+/g, ' ').trim();
|
||
}
|
||
const parts = [];
|
||
if (row.author) parts.push(row.author);
|
||
if (row.title) parts.push(row.title);
|
||
if (row.joura) parts.push(row.joura);
|
||
if (row.dateno) parts.push(row.dateno);
|
||
if (row.doilink) parts.push('Available at: ' + row.doilink);
|
||
return parts.join(' ').replace(/\s+/g, ' ').trim();
|
||
}
|
||
|
||
function normalizeWordRefBatchRow(row) {
|
||
if (!row) return row;
|
||
let normalized = normalizeImportedReferenceFields(row);
|
||
if (normalized.doilink) {
|
||
normalized.doilink = normalizeDoilinkFull(normalized.doilink);
|
||
normalized.doi = normalizeDoiValue(normalized.doilink);
|
||
}
|
||
if (normalized.dateno) {
|
||
normalized.dateno = normalized.dateno.replace(/^(\d{4}):/, '$1;');
|
||
}
|
||
normalized = finalizeReferenceFields(normalized, row.wordHtml || normalized.content || '');
|
||
return Object.assign({}, row, normalized);
|
||
}
|
||
|
||
/** 按序号将 Word 条目与现有参考文献一一对应 */
|
||
export function buildWordRefBatchRows(existingRows, uploadedRows) {
|
||
const existing = existingRows || [];
|
||
const uploaded = uploadedRows || [];
|
||
const maxLen = Math.max(existing.length, uploaded.length);
|
||
const rows = [];
|
||
|
||
for (let i = 0; i < maxLen; i++) {
|
||
const existingRow = existing[i] || null;
|
||
const uploadedRow = uploaded[i] || null;
|
||
const fields = (uploadedRow && uploadedRow.fields) || null;
|
||
const prevExistingRow = i > 0 ? existing[i - 1] : null;
|
||
rows.push(
|
||
normalizeWordRefBatchRow({
|
||
index: i + 1,
|
||
apply: !!(uploadedRow && fields),
|
||
p_refer_id: existingRow ? existingRow.p_refer_id : null,
|
||
pre_p_refer_id: existingRow
|
||
? existingRow.pre_p_refer_id
|
||
: prevExistingRow
|
||
? prevExistingRow.p_refer_id
|
||
: null,
|
||
existingType: existingRow ? existingRow.refer_type : '',
|
||
existingPreview: previewExistingReference(existingRow),
|
||
refer_type: fields ? fields.refer_type : uploadedRow ? uploadedRow.refer_type : 'journal',
|
||
author: fields ? fields.author : '',
|
||
title: fields ? fields.title : '',
|
||
joura: fields ? fields.joura : '',
|
||
dateno: fields ? fields.dateno : '',
|
||
doilink: fields ? fields.doilink : '',
|
||
doi: fields ? fields.doi || fields.doilink : '',
|
||
isbn: fields ? fields.isbn : '',
|
||
content: fields ? fields.content : '',
|
||
wordHtml: uploadedRow ? uploadedRow.html : '',
|
||
unmatched: !existingRow || !uploadedRow,
|
||
missingExisting: !existingRow,
|
||
missingUploaded: !uploadedRow
|
||
})
|
||
);
|
||
}
|
||
|
||
return rows;
|
||
}
|
||
|
||
/** 调用 dealContent 精修字段拆解 */
|
||
export async function enrichWordRefBatchRowsWithDealContent(apiClient, rows) {
|
||
const list = rows || [];
|
||
const updated = [];
|
||
for (let i = 0; i < list.length; i++) {
|
||
const row = list[i];
|
||
if (!row || !row.wordHtml) {
|
||
updated.push(normalizeWordRefBatchRow(row));
|
||
continue;
|
||
}
|
||
try {
|
||
const content = normalizeImportedReferenceText(htmlToPlainText(row.wordHtml));
|
||
const res = await apiClient.post('api/References/dealContent', { content: content });
|
||
const data = res && res.data ? res.data : null;
|
||
if (data) {
|
||
const merged = applyDealContentToFields(
|
||
{
|
||
refer_type: row.refer_type,
|
||
author: row.author,
|
||
title: row.title,
|
||
joura: row.joura,
|
||
dateno: row.dateno,
|
||
doilink: row.doilink,
|
||
doi: row.doi || row.doilink,
|
||
isbn: row.isbn,
|
||
content: row.content
|
||
},
|
||
data
|
||
);
|
||
const nextRow = normalizeWordRefBatchRow(
|
||
Object.assign({}, row, merged, {
|
||
refer_type: merged.refer_type || row.refer_type,
|
||
doi: merged.doi || merged.doilink || row.doi || row.doilink
|
||
})
|
||
);
|
||
updated.push(nextRow);
|
||
continue;
|
||
}
|
||
} catch (err) {
|
||
// 保留本地拆解结果
|
||
}
|
||
updated.push(normalizeWordRefBatchRow(row));
|
||
}
|
||
return updated;
|
||
}
|
||
|
||
function normalizeDoiValue(doilink) {
|
||
let raw = String(doilink || '').trim();
|
||
if (!raw) return '';
|
||
raw = raw.replace(/^https?:\/\/doi\.org\//i, '');
|
||
raw = raw.replace(/^doi:/i, '');
|
||
return raw.toLowerCase();
|
||
}
|
||
|
||
function normalizeDoilinkFull(doilink) {
|
||
const raw = String(doilink || '').trim();
|
||
if (!raw) return '';
|
||
if (/^https?:\/\//i.test(raw)) return raw;
|
||
const doi = normalizeDoiValue(raw);
|
||
return doi ? 'https://doi.org/' + doi : raw;
|
||
}
|
||
|
||
function appendReferPart(content, part) {
|
||
const piece = String(part || '').trim();
|
||
if (!piece) return content;
|
||
let out = String(content || '').trim();
|
||
if (!out) return piece;
|
||
if (out.endsWith('.')) {
|
||
return out + piece;
|
||
}
|
||
return out + '.' + piece;
|
||
}
|
||
|
||
/** journal 的 content 字段,与 editRefer 接口一致 */
|
||
export function buildJournalReferContent(fields) {
|
||
const author = String((fields && fields.author) || '').trim();
|
||
const title = String((fields && fields.title) || '').trim();
|
||
const joura = String((fields && fields.joura) || '').trim();
|
||
const dateno = String((fields && fields.dateno) || '').trim();
|
||
const doilink = normalizeDoilinkFull(fields && fields.doilink);
|
||
|
||
let content = author;
|
||
if (title) {
|
||
if (!content) {
|
||
content = title;
|
||
} else if (content.endsWith('.')) {
|
||
content = content + ' ' + title;
|
||
} else {
|
||
content = content + '. ' + title;
|
||
}
|
||
}
|
||
if (joura) {
|
||
content = content + ' ' + joura;
|
||
if (!/\.\s*$/.test(joura)) {
|
||
content = content + '.';
|
||
}
|
||
}
|
||
if (dateno) {
|
||
const datenoForContent = dateno.replace(/^(\d{4});/, '$1:');
|
||
content = appendReferPart(content, datenoForContent);
|
||
}
|
||
if (doilink) {
|
||
content = content + '.Available at: ' + doilink;
|
||
}
|
||
return content;
|
||
}
|
||
|
||
/** book 的 content 字段 */
|
||
export function buildBookReferContent(fields) {
|
||
const author = String((fields && fields.author) || '').trim();
|
||
const title = String((fields && fields.title) || '').trim();
|
||
const dateno = String((fields && fields.dateno) || '').trim();
|
||
const isbn = String((fields && fields.isbn) || '').trim();
|
||
|
||
let content = author;
|
||
if (title) {
|
||
content = appendReferPart(content, title);
|
||
}
|
||
if (dateno) {
|
||
content = appendReferPart(content, dateno);
|
||
}
|
||
if (isbn) {
|
||
content = content + '. Available at: ISBN: ' + isbn;
|
||
}
|
||
return content;
|
||
}
|
||
|
||
export function buildEditReferPayload(row, pArticleId) {
|
||
const normalized = normalizeWordRefBatchRow(row || {});
|
||
const referType = String(normalized.refer_type || 'journal').toLowerCase();
|
||
const base = {
|
||
p_article_id: pArticleId != null && pArticleId !== '' ? pArticleId : '',
|
||
p_refer_id: normalized.p_refer_id,
|
||
pre_p_refer_id: normalized.pre_p_refer_id != null && normalized.pre_p_refer_id !== '' ? normalized.pre_p_refer_id : ''
|
||
};
|
||
|
||
if (referType === 'book') {
|
||
return Object.assign(base, {
|
||
doi: '',
|
||
refer_type: 'book',
|
||
author: normalized.author || '',
|
||
title: normalized.title || '',
|
||
dateno: normalized.dateno || '',
|
||
isbn: normalized.isbn || '',
|
||
joura: '',
|
||
doilink: '',
|
||
content: buildBookReferContent(normalized)
|
||
});
|
||
}
|
||
|
||
if (referType === 'other') {
|
||
return Object.assign(base, {
|
||
doi: normalizeDoiValue(normalized.doilink || normalized.doi),
|
||
refer_type: 'other',
|
||
author: '',
|
||
title: '',
|
||
dateno: '',
|
||
joura: '',
|
||
doilink: normalizeDoilinkFull(normalized.doilink) || '',
|
||
isbn: '',
|
||
content: normalized.content || ''
|
||
});
|
||
}
|
||
|
||
const doilink = normalizeDoilinkFull(normalized.doilink);
|
||
const doi = normalizeDoiValue(normalized.doilink || normalized.doi);
|
||
return Object.assign(base, {
|
||
doi: doi,
|
||
refer_type: 'journal',
|
||
author: normalized.author || '',
|
||
title: normalized.title || '',
|
||
joura: normalized.joura || '',
|
||
dateno: normalized.dateno || '',
|
||
doilink: doilink,
|
||
isbn: '',
|
||
content: buildJournalReferContent(normalized)
|
||
});
|
||
}
|
||
|
||
export function extractAddedReferId(data) {
|
||
if (!data || typeof data !== 'object') {
|
||
return '';
|
||
}
|
||
if (data.p_refer_id != null && data.p_refer_id !== '') {
|
||
return String(data.p_refer_id);
|
||
}
|
||
return '';
|
||
}
|
||
|
||
/** 新增参考文献 payload,p_refer_id 留空,pre_p_refer_id 指向前一条 */
|
||
export function buildAddReferPayload(row, pArticleId, preReferId) {
|
||
const payload = buildEditReferPayload(row, pArticleId);
|
||
delete payload.p_refer_id;
|
||
payload.pre_p_refer_id = preReferId != null && preReferId !== '' ? String(preReferId) : '';
|
||
return payload;
|
||
}
|
||
|
||
export function canWordRefBatchRowSave(row) {
|
||
if (!row || row.missingUploaded) {
|
||
return false;
|
||
}
|
||
if (row.p_refer_id) {
|
||
return true;
|
||
}
|
||
return !!row.missingExisting;
|
||
}
|
||
|
||
const WORD_REF_BATCH_FIELD_DEFS = {
|
||
journal: ['author', 'title', 'joura', 'dateno', 'doilink'],
|
||
book: ['author', 'title', 'dateno', 'isbn'],
|
||
other: ['content']
|
||
};
|
||
|
||
const WORD_REF_BATCH_REQUIRED_FIELDS = {
|
||
journal: ['title', 'joura', 'dateno'],
|
||
book: ['author', 'title', 'dateno'],
|
||
other: ['content']
|
||
};
|
||
|
||
export function getWordRefBatchFieldKeys(referType) {
|
||
const type = String(referType || 'journal').toLowerCase();
|
||
return (WORD_REF_BATCH_FIELD_DEFS[type] || WORD_REF_BATCH_FIELD_DEFS.journal).slice();
|
||
}
|
||
|
||
export function isWordRefBatchRowParseIncomplete(row) {
|
||
if (!row || row.missingUploaded) {
|
||
return false;
|
||
}
|
||
const type = String(row.refer_type || 'journal').toLowerCase();
|
||
const required = WORD_REF_BATCH_REQUIRED_FIELDS[type] || WORD_REF_BATCH_REQUIRED_FIELDS.journal;
|
||
return required.some(function (key) {
|
||
return !String(row[key] || '').trim();
|
||
});
|
||
}
|
||
|
||
export function stripWordRefBatchHtmlPreview(html) {
|
||
return htmlToPlainText(String(html || ''))
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
}
|
||
|
||
/** 切换为 other 时:用 Word 原文(或已拆字段拼接)填入 content */
|
||
export function buildWordRefBatchContentFromRow(row) {
|
||
if (!row) {
|
||
return '';
|
||
}
|
||
const fromWord = stripWordRefBatchHtmlPreview(row.wordHtml);
|
||
if (fromWord) {
|
||
return normalizeImportedReferenceText(fromWord);
|
||
}
|
||
|
||
const parts = [];
|
||
if (row.author) {
|
||
parts.push(String(row.author).trim());
|
||
}
|
||
if (row.title) {
|
||
parts.push(String(row.title).trim());
|
||
}
|
||
if (row.joura) {
|
||
parts.push(String(row.joura).trim());
|
||
}
|
||
if (row.dateno) {
|
||
parts.push(String(row.dateno).trim());
|
||
}
|
||
|
||
let content = parts
|
||
.filter(Boolean)
|
||
.join(' ')
|
||
.replace(/\s+\./g, '.')
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
|
||
const doilink = String(row.doilink || '').trim();
|
||
if (doilink && !/Available at:/i.test(content)) {
|
||
content = content + '. Available at: ' + doilink;
|
||
}
|
||
|
||
return normalizeImportedReferenceText(content || row.content || '');
|
||
}
|
||
|
||
export function applyWordRefBatchOtherType(row) {
|
||
if (!row) {
|
||
return row;
|
||
}
|
||
row.refer_type = 'other';
|
||
row.content = buildWordRefBatchContentFromRow(row);
|
||
const plain = String(row.content || '');
|
||
const urlMatch = plain.match(/Available at:\s*(https?:\/\/\S+)/i);
|
||
if (urlMatch) {
|
||
row.doilink = urlMatch[1].replace(/[.,;)\]]+$/, '');
|
||
}
|
||
return row;
|
||
}
|
||
|
||
/**
|
||
* 按序号批量保存:已有条目走 editRefer,Word 多出的条目走 addReferByParticleid,
|
||
* 每条成功后用返回/已有的 p_refer_id 作为下一条的 pre_p_refer_id。
|
||
*/
|
||
export async function saveWordRefBatchRows(apiClient, options) {
|
||
const opts = options || {};
|
||
const rows = (opts.rows || [])
|
||
.filter(function (row) {
|
||
return row && row.apply && canWordRefBatchRowSave(row);
|
||
})
|
||
.sort(function (a, b) {
|
||
return (a.index || 0) - (b.index || 0);
|
||
});
|
||
|
||
const pArticleId = opts.pArticleId;
|
||
const useUserAdd = opts.role === 'user';
|
||
const addEndpoint = useUserAdd ? 'api/Preaccept/addRefer' : 'api/Preaccept/addReferByParticleid';
|
||
|
||
let lastReferId = null;
|
||
let ok = 0;
|
||
let fail = 0;
|
||
|
||
for (let i = 0; i < rows.length; i++) {
|
||
const row = rows[i];
|
||
try {
|
||
if (row.p_refer_id) {
|
||
const payload = buildEditReferPayload(row, pArticleId);
|
||
if (lastReferId != null && lastReferId !== '') {
|
||
payload.pre_p_refer_id = lastReferId;
|
||
}
|
||
const res = await apiClient.post('api/Preaccept/editRefer', payload);
|
||
if (res && res.code == 0) {
|
||
ok += 1;
|
||
lastReferId = String(row.p_refer_id);
|
||
} else {
|
||
fail += 1;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
if (row.missingExisting) {
|
||
const payload = buildAddReferPayload(row, pArticleId, lastReferId);
|
||
if (useUserAdd && opts.articleId != null && opts.articleId !== '') {
|
||
payload.article_id = opts.articleId;
|
||
}
|
||
const res = await apiClient.post(addEndpoint, payload);
|
||
if (res && res.code == 0) {
|
||
ok += 1;
|
||
const newId = extractAddedReferId(res.data);
|
||
if (newId) {
|
||
lastReferId = newId;
|
||
}
|
||
} else {
|
||
fail += 1;
|
||
}
|
||
continue;
|
||
}
|
||
|
||
fail += 1;
|
||
} catch (err) {
|
||
fail += 1;
|
||
}
|
||
}
|
||
|
||
return { ok: ok, fail: fail };
|
||
}
|