审稿人证书生成
This commit is contained in:
485
src/utils/wordReferenceBatchImport.js
Normal file
485
src/utils/wordReferenceBatchImport.js
Normal file
@@ -0,0 +1,485 @@
|
||||
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 };
|
||||
}
|
||||
Reference in New Issue
Block a user