diff --git a/package-lock.json b/package-lock.json index cdadde9..0eb65d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2178,13 +2178,10 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", - "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", "dev": true, - "dependencies": { - "regenerator-runtime": "^0.14.0" - }, "engines": { "node": ">=6.9.0" } diff --git a/src/api/index.js b/src/api/index.js index 487fb66..525753d 100644 --- a/src/api/index.js +++ b/src/api/index.js @@ -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: '/', //正式 }); diff --git a/src/common/js/commonJS.js b/src/common/js/commonJS.js index 9724aca..b4c01ba 100644 --- a/src/common/js/commonJS.js +++ b/src/common/js/commonJS.js @@ -7,6 +7,7 @@ import { MathfieldElement } from 'mathlive'; import 'mathlive/dist/mathlive-static.css'; import 'mathlive/dist/mathlive-fonts.css'; import { importWordDocumentWithMath as parseWordDocumentWithMath, parseHtmlToLatex as convertHtmlToLatex, postProcessImportedWordHtml, parseImportedHtmlToContentRows, mergeAdjacentBlueTags, normalizeSpacesAroundBlueTags } from '@/utils/wordMathImport'; +import { wrapReferenceHighlightsInBlueHtml } from '@/utils/manuscriptMediaReferences'; import api from '../../api/index.js'; import Common from '@/components/common/common' import Tiff from 'tiff.js'; @@ -852,32 +853,8 @@ export default { // 首字母大写 str = capitalizeFirstLetter(str); - // 添加蓝色标签 - // 1. 修改正则,只匹配不在 标签内的 [数字] -// 这里的思路是:匹配 [内容],但通过逻辑过滤掉已经被包裹的情况 -const regex = /\[(\d+(?:–\d+)?(?:, ?\d+(?:–\d+)?)*)\]/g; - -// 注意:不要在最上面执行 str.replace(//g, ''),否则之前的标记就全白做了 - -str = str.replace(regex, function (match, content, offset, fullString) { - // 【关键判断】:检查匹配位置的前后,是否已经存在 - const prefix = fullString.substring(offset - 6, offset); // 是 6 位 - const suffix = fullString.substring(offset + match.length, offset + match.length + 7); // 是 7 位 - - if (prefix === '' && suffix === '') { - return match; // 如果已经有标签了,原样返回,不重复标记 - } - - // 判断逻辑:纯数字、逗号空格、连字符 - if (/^\d+$/.test(content) || /, ?/.test(content) || /–/.test(content)) { - return `${match}`; - } - - return match; -}); - - - + // 添加蓝色标签:参考文献标号 + Figure/Table 引用 + str = wrapReferenceHighlightsInBlueHtml(str); return str; @@ -1581,6 +1558,7 @@ str = str.replace(regex, function (match, content, offset, fullString) { text = text.replace(/<[^\/>]+>\s*<\/[^>]+>/gi, (match) => (match.trim() === '' ? '' : match)); text = mergeAdjacentBlueTags(text); text = normalizeSpacesAroundBlueTags(text); + text = wrapReferenceHighlightsInBlueHtml(text); parsedData.push(text.trim() === '' ? '' : text); }); diff --git a/src/components/common/Sidebar.vue b/src/components/common/Sidebar.vue index 4888045..d5082e9 100644 --- a/src/components/common/Sidebar.vue +++ b/src/components/common/Sidebar.vue @@ -62,7 +62,17 @@ @@ -237,6 +247,7 @@ export default { menuList: [], /** 青年编委申请红点:Young Scientist 父级 + Apply 子项,数据来自 getYboardApplys */ applyBadgeYouth: 0, + applyBadgeFeeApproval: 0, items: [], // 作者 author_items: [ @@ -333,7 +344,6 @@ export default { title: this.$t('menu.userManSys6') }, { - //论文出版监督 icon: 'el-icon-lx-copy', index: 'JournalCitationAnalysis', title: this.$t('menu.JournalCitationAnalysis') @@ -773,74 +783,26 @@ export default { //超级管理员 this.items = this.admin_items; } + if (this.isFeeApprovalAccount()) { + this.items.push({ + icon: 'el-icon-money', + index: 'journalFeeApproval', + title: this.$t('menu.journalFeeApproval') + }); + this.fetchFeeApprovalBadge(); + } + if (this.isJiajianAccount()) { + if (!this.user_cap.includes('superadmin')) { + this.injectChiefInspectorMenu(); + } + } if (this.user_cap.includes('superadmin')) { var superadminData = []; this.items.splice( 3, 0, - - { - icon: 'el-icon-s-platform', - index: '13', - title: this.$t('sidebar.chiefInspector'), - subs: [ - { - index: 'Classificationmanagement', - title: this.$t('sidebar.journalManagement'), - subs: [ - { - index: 'JournalManagementAll', - title: this.$t('sidebar.journalList') - }, - - { - index: 'GroupClassification', - title: this.$t('sidebar.GroupClassification') - } - ] - }, - - { - index: 'Academicresourcesupervise', - title: this.$t('menu.Academicresourcesupervise'), - subs: [ - { - index: 'editorialBoard', - title: this.$t('sidebar.editorialBoard1') - }, - { - index: 'superYoungScientistManagement', - title: this.$t('sidebar.editorialBoard2') - }, - { - index: 'superJournalsManagement', - title: this.$t('sidebar.editorialBoard3') - } - ] - }, - { - index: 'publicationsupervise', - title: this.$t('menu.publicationsupervise'), - subs: [ - { - index: 'allPaperSubmitanalysis', - // index: 'superYoungScientistManagement', - title: this.$t('menu.papersubmit') - }, - - { - index: '18', - title: this.$t('menu.ArticlePublication') - } - ] - }, - { - index: 'allJournalCitationAnalysis', - title: this.$t('menu.JournalCitationAnalysis') - } - ] - } + this.getChiefInspectorMenuItem() ); this.items = [ @@ -868,13 +830,88 @@ export default { bus.$on('apply-badge-refresh', () => { this.fetchApplyBadgeSummary(); }); + bus.$on('fee-approval-badge-refresh', () => { + this.fetchFeeApprovalBadge(); + }); }, beforeDestroy() { bus.$off('apply-badge-refresh'); + bus.$off('fee-approval-badge-refresh'); }, methods: { // 获取数据 getDate() {}, + isFeeApprovalAccount() { + return String(localStorage.getItem('U_id') || '') === '24950'; + }, + isJiajianAccount() { + const name = String(localStorage.getItem('U_name') || '').toLowerCase(); + const uid = String(localStorage.getItem('U_id') || ''); + return name === 'jiajian' || uid === '29578'; + }, + getChiefInspectorMenuItem() { + return { + icon: 'el-icon-s-platform', + index: '13', + title: this.$t('sidebar.chiefInspector'), + subs: [ + { + index: 'Classificationmanagement', + title: this.$t('sidebar.journalManagement'), + subs: [ + { + index: 'JournalManagementAll', + title: this.$t('sidebar.journalList') + }, + { + index: 'GroupClassification', + title: this.$t('sidebar.GroupClassification') + } + ] + }, + { + index: 'Academicresourcesupervise', + title: this.$t('menu.Academicresourcesupervise'), + subs: [ + { + index: 'editorialBoard', + title: this.$t('sidebar.editorialBoard1') + }, + { + index: 'superYoungScientistManagement', + title: this.$t('sidebar.editorialBoard2') + }, + { + index: 'superJournalsManagement', + title: this.$t('sidebar.editorialBoard3') + } + ] + }, + { + index: 'publicationsupervise', + title: this.$t('menu.publicationsupervise'), + subs: [ + { + index: 'allPaperSubmitanalysis', + title: this.$t('menu.papersubmit') + }, + { + index: '18', + title: this.$t('menu.ArticlePublication') + } + ] + }, + { + index: 'allJournalCitationAnalysis', + title: this.$t('menu.JournalCitationAnalysis') + } + ] + }; + }, + injectChiefInspectorMenu() { + if (this.items.some((item) => item.index === '13')) return; + this.items.splice(3, 0, this.getChiefInspectorMenuItem()); + }, fetchApplyBadgeSummary() { if (String(this.userrole) !== '1') return; const editorId = localStorage.getItem('U_id'); @@ -891,6 +928,127 @@ export default { .catch(() => { this.applyBadgeYouth = 0; }); + }, + fetchFeeApprovalBadge() { + if (!this.isFeeApprovalAccount()) return; + this.$api + .post('api/Order/getApplyCount', {}) + .then((res) => { + if (res && res.code === 0) { + const pendingCount = this.extractFeeApplyPendingCount(res.data); + this.applyBadgeFeeApproval = pendingCount > 0 ? 1 : 0; + if (pendingCount > 0) { + this.showFeeApprovalNotifyOnce(pendingCount); + } + } else { + this.applyBadgeFeeApproval = 0; + } + }) + .catch(() => { + this.applyBadgeFeeApproval = 0; + }); + }, + showFeeApprovalNotifyOnce(pendingCount) { + const uid = String(localStorage.getItem('U_id') || ''); + const storageKey = 'feeApprovalNotifyShown_' + uid; + if (!uid || sessionStorage.getItem(storageKey)) return; + if (this.$route.path === '/journalFeeApproval') { + sessionStorage.setItem(storageKey, '1'); + return; + } + sessionStorage.setItem(storageKey, '1'); + const h = this.$createElement; + this.$notify({ + title: this.$t('journalFeeApproval.notifyTitle'), + message: h( + 'div', + { + style: { + width: '260px', + cursor: 'pointer' + }, + on: { + click: () => { + this.$router.push('/journalFeeApproval'); + } + } + }, + [ + h( + 'p', + { + style: { + color: '#006699', + margin: '0 0 6px', + lineHeight: '1.5' + } + }, + this.$t('journalFeeApproval.notifyMessage', { count: pendingCount }) + ), + h( + 'p', + { + style: { + color: '#909399', + margin: 0, + fontSize: '12px', + lineHeight: '1.5' + } + }, + this.$t('journalFeeApproval.notifyHint') + ) + ] + ), + type: 'warning', + position: 'bottom-right', + duration: 3000, + onClick: () => { + this.$router.push('/journalFeeApproval'); + } + }); + }, + extractFeeApplyList(data) { + if (Array.isArray(data)) return data; + if (!data || typeof data !== 'object') return []; + return data.list || data.applies || data.applys || data.items || data.data || []; + }, + extractFeeApplyPendingCount(data) { + if (!data) return 0; + if (Array.isArray(data)) { + const pendingItem = data.find(function (item) { + if (!item || typeof item !== 'object') return false; + const state = item.state !== undefined ? item.state : item.status; + return Number(state) === 0; + }); + if (pendingItem) { + return Number(pendingItem.count != null ? pendingItem.count : pendingItem.num != null ? pendingItem.num : pendingItem.total) || 0; + } + return 0; + } + if (typeof data === 'object') { + return ( + Number( + data.pending != null + ? data.pending + : data.wait != null + ? data.wait + : data.pending_count != null + ? data.pending_count + : data.state0 != null + ? data.state0 + : data['0'] != null + ? data['0'] + : 0 + ) || 0 + ); + } + return 0; + }, + isFeeApplyPending(item) { + if (!item || typeof item !== 'object') return false; + const status = item.status !== undefined ? item.status : item.state; + if (status === undefined || status === null || status === '') return true; + return status === 0 || status === '0' || status === 'pending' || status === 'wait' || status === 'waiting'; } } }; @@ -999,4 +1157,16 @@ export default { color: inherit; vertical-align: middle; } + +.sidebar-menu-fee-approval-badge { + display: inline-block; + vertical-align: middle; +} + +.sidebar-menu-fee-approval-badge ::v-deep .el-badge__content.is-dot { + top: 50%; + right: -8px; + border: 0; + background-color: #f56c6c; +} diff --git a/src/components/common/common.vue b/src/components/common/common.vue index a1781ec..9bac70a 100644 --- a/src/components/common/common.vue +++ b/src/components/common/common.vue @@ -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' //测试环境 diff --git a/src/components/common/langs/en.js b/src/components/common/langs/en.js index b130c84..dcbc657 100644 --- a/src/components/common/langs/en.js +++ b/src/components/common/langs/en.js @@ -80,6 +80,7 @@ const en = { PaperSubmitanalysis: 'Paper Submit Analysis', ArticlePublicationanalysis: 'Article Publication Analysis', journalArticleCount: 'Article Count', + journalFeeApproval: 'Journal Fee Approval', Promotionsystem: 'Promotion System', Userdatabase: 'User Database', analysis: 'Article Analysis', @@ -1211,6 +1212,112 @@ const en = { citeRelevanceCopyGroup: 'Copy paragraph', citeRelevanceDownloadHtml: 'Download HTML', citeRelevanceDownloadHtmlSuccess: 'HTML downloaded', + citeRelevanceWithUploadedRefs: 'Group export will use {n} uploaded reference(s)', + citeRelevanceDownloadHtmlSuccessWithUploaded: 'HTML downloaded (based on {n} uploaded reference(s))', + refAnnotationReportTip: 'Download annotated HTML relevance report (run AI check first)', + refAnnotationReportShort: 'Report', + exportRelevanceWordTip: 'Export Word with citation relevance comments (requires AI audit; yellow/red items become comments)', + exportRelevanceWordShort: 'Rel. Word', + exportRelevanceWordSuccess: 'Word with relevance comments downloaded', + exportRelevanceWordFail: 'Failed to export Word with relevance comments', + mediaRefCheckWarn: 'Media reference check: body cites missing {list}', + mediaRefCheckMore: ' ({n} total)', + wordRefsPreviewShort: 'Word refs', + wordRefsPreviewTitle: 'Original Word · References', + wordRefsPreviewTotal: 'Total: {count} references', + wordRefsPreviewClose: 'Close', + wordRefsPreviewCopy: 'Copy plain text', + wordRefsPreviewCopySuccess: 'References copied', + wordRefsPreviewCopyFail: 'Copy failed', + wordRefsPreviewNoArticle: 'Missing article ID — cannot load Word manuscript', + wordRefsPreviewNoManuscript: 'Manuscript Word file not found (gridData / getFilesForArticle)', + wordRefsPreviewEmpty: 'No References section found in the original Word manuscript', + wordRefsPreviewFail: 'Failed to load Word references', + wordRefBatchUploadShort: 'Upload proofread Word', + wordRefBatchUploadTip: 'Upload proofread Word references, map fields by index, and batch save', + wordRefBatchDialogTitle: 'Word references · field mapping', + wordRefBatchApply: 'Apply', + wordRefBatchStatus: 'Status', + wordRefBatchType: 'Type', + wordRefBatchAuthor: 'Author', + wordRefBatchTitle: 'Title', + wordRefBatchJournal: 'Journal', + wordRefBatchDateno: 'Publication', + wordRefBatchDoi: 'DOI/URL', + wordRefBatchExisting: 'Current in system', + wordRefBatchMatched: 'Matched', + wordRefBatchMissing: 'Missing in Word', + wordRefBatchExtra: 'No system row', + wordRefBatchSelectAll: 'Select all', + wordRefBatchSelectNone: 'Select none', + wordRefBatchReparse: 'Re-parse fields (server)', + wordRefBatchReparseSuccess: 'Fields re-parsed', + wordRefBatchReparseFail: 'Field parsing failed', + wordRefBatchSave: 'Save selected', + wordRefBatchSaveSuccess: 'Saved {ok}, failed {fail}', + wordRefBatchSaveFail: 'Batch save failed', + wordRefBatchNothingSelected: 'Select at least one reference to save', + wordRefBatchParseFail: 'Failed to parse Word references', + wordRefBatchCountMismatch: 'Count mismatch: system {existing}, Word {uploaded}. Please verify before saving.', + wordRefBatchClickToEdit: 'Click text to edit', + wordRefBatchFieldEmpty: 'Click to fill', + wordRefBatchShowExisting: 'Compare', + wordRefBatchHideExisting: 'Hide', + wordRefBatchParseFailed: 'Incomplete parsing — please fill in each field', + wordRefBatchWordOriginal: 'Word original', + refAnnotationReportSuccess: 'Annotation report HTML downloaded', + refAnnotationReportFail: 'Failed to download annotation report', + refAnnotationReportNoArticle: 'Missing article ID — cannot load AI check data', + refAnnotationReportNoData: 'No AI relevance results yet — run the check on Reference Conversion first', + refAnnotationReportTitleSuffix: 'Review report', + refAnnotationReportManuscriptPreview: 'Manuscript preview: {id}', + refAnnotationReportRefMatchPrefix: 'Reference source: ', + refAnnotationReportRefLibraryPrefix: 'Reference library: ', + refAnnotationReportRefTotal: '{n} entries', + refAnnotationReportAnalysisTitle: 'Reference analysis', + refAnnotationReportStatTotal: 'Reviewed', + refAnnotationReportStatDirect: 'Direct match', + refAnnotationReportStatStrong: 'Strong match', + refAnnotationReportStatWeak: 'Weak match', + refAnnotationReportStatNone: 'Not relevant', + refAnnotationReportLocateTip: 'Locate and highlight the paragraph on the left', + refAnnotationReportLocatePara: 'Paragraph {n} on the left (click to locate)', + refAnnotationReportParaRefsPrefix: 'Citations in paragraph: ', + refAnnotationReportVerdictLabel: 'Verdict: ', + refAnnotationReportReasonLabel: 'Reason: ', + refAnnotationReportAuthorCommentLabel: 'Editor comment: ', + refAnnotationReportNoReason: 'No details', + refAnnotationReportConclusionLabel: 'Conclusion: ', + refAnnotationReportConclusionKeep: 'Keep', + refAnnotationReportConclusionRevise: 'Revise suggested', + refAnnotationReportNoConclusion: 'No conclusion', + refAnnotationReportScoreLabel: 'Relevance: {score}%', + refAnnotationReportCiteLocLabel: 'Citation {n}', + refAnnotationReportCiteParaSuffix: ' · Para {para}', + refAnnotationReportParaSectionTitle: 'By paragraph', + refAnnotationReportRefConclusionSectionTitle: 'References · all review conclusions', + refAnnotationReportRefCiteTotal: '{n} in-text citation(s)', + refAnnotationReportSummaryTitle: 'AI summary: ', + refAnnotationReportSummaryPrefix: 'The following references are weakly related; please verify: ', + refAnnotationReportSummaryRefPrefix: 'Ref. ', + refAnnotationReportWarnBadge: 'Weak / irrelevant citations found', + refAnnotationReportIssueNavTitle: 'Paragraphs needing attention', + refAnnotationReportIssueNavHint: 'Click a paragraph number to jump to review notes', + refAnnotationReportIssueCountLabel: '{n} revise suggested', + refAnnotationReportJumpToReview: 'View notes →', + refAnnotationReportJumpToReviewTip: 'Jump to review notes for this paragraph', + refAnnotationReportLocateSource: '↩ Locate source', + refAnnotationReportParaBadge: 'Paragraph {n}', + refAnnotationReportParaOkLabel: 'Citations OK', + refAnnotationReportLegendIssue: 'Red border: paragraph has citations to revise', + refAnnotationReportLegendOk: 'Normal paragraph: citations checked and OK', + refAnnotationReportLegendCite: 'Click citation no. or “View notes” to jump to the right panel', + refAnnotationReportNoData: 'No AI relevance results yet. Run the check on Reference Conversion first.', + refAnnotationReportDefaultTitle: 'manuscript', + refAnnotationReportLevelDirect: 'Direct match', + refAnnotationReportLevelStrong: 'Strong match', + refAnnotationReportLevelWeak: 'Weak match', + refAnnotationReportLevelNone: 'Not relevant', refHtmlCopy: 'Copy references', refHtmlCopyShort: 'Copy', refHtmlCopySuccess: 'References copied (numbering, italic journal, blue DOI, Available at line break)', @@ -1251,6 +1358,10 @@ const en = { 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', + refHtmlCopyBracket: 'Copy [n] format', + refHtmlCopyBracketSuccess: 'Copied to clipboard ([n] numbering, line break after Available at:)', + refHtmlCopyBracketEmpty: 'Nothing to copy in the editor', + refHtmlCopyBracketFail: 'Copy failed — please select and copy manually', exportImg: 'Export PNG', PaperRotation: 'Paper Rotation', removeAnnotations: 'Are you sure you want to delete this Annotation?', @@ -1345,6 +1456,7 @@ const en = { youhui: 'Final price', discountprice: 'Final price after discount', youhuiremark: 'Discount description', + applyPrice: 'Apply', submitOrder: 'Make a payment', state0: 'Pending payment', state1: 'Payment successfully', @@ -1358,6 +1470,71 @@ const en = { Paymentstatus: 'Payment status', time: 'Payment time', }, + journalFeeApproval: { + title: 'Journal Fee Approval', + pendingList: 'Pending Price Change Requests', + pageDesc: 'Review APC change requests submitted by editors. Search by article title or SN, verify the original fee, requested fee, and remark, then approve or reject.', + searchLabel: 'Search', + colArticleInfo: 'Article Information', + colPriceChange: 'Price Change Details', + colApproval: 'Approval', + historyTitle: 'Application History', + no: 'No.', + articleId: 'Article ID', + journal: 'Journal', + articleTitle: 'Article Title', + sn: 'SN', + originalFee: 'Original APC (USD)', + applyFee: 'Requested APC (USD)', + remark: 'Change Remark', + status: 'Status', + operator: 'Applicant', + applyTime: 'Applied At', + action: 'Approval', + accept: 'Approve', + reject: 'Reject', + cancel: 'Cancel', + tip: 'Tip', + acceptConfirm: 'Approve this price change request?', + rejectTitle: 'Reject Application', + rejectRemark: 'Reject Remark', + rejectRemarkPlaceholder: 'Please enter the reason for rejection', + rejectRemarkRequired: 'Reject remark is required', + noPending: 'No pending applications', + noAccepted: 'No approved applications', + noRejected: 'No rejected applications', + tabPending: 'Pending', + tabAccepted: 'Approved', + tabRejected: 'Rejected', + noHistory: 'No application history', + viewHistory: 'Application History', + refresh: 'Refresh', + search: 'Search', + searchPlaceholder: 'Enter article title or SN', + noSearchResult: 'No matching pending applications', + currentArticle: 'Current article', + selectArticleHint: 'Click a row in the pending list, or enter an article ID to view history', + articleIdPlaceholder: 'Article ID', + searchHistory: 'Search', + articleIdRequired: 'Please enter an article ID', + missingArticleId: 'Missing article_id on this record', + historyLoadFailed: 'Failed to load application history', + feeChange: 'Fee Change', + loadFailed: 'Failed to load data', + actionFailed: 'Operation failed', + acceptSuccess: 'Application approved', + rejectSuccess: 'Application rejected', + noPermission: 'You do not have permission to access this page', + statusApply: 'Submitted', + statusAccept: 'Approved', + statusReject: 'Rejected', + statusPending: 'Pending', + statusOther: 'Other', + viewArticleDetail: 'Click to view article details', + notifyTitle: 'Journal Fee Approval', + notifyMessage: 'You have {count} pending price change request(s)', + notifyHint: 'Click to open the approval page', + }, PreAccept: { successInfo: 'Congratulations! Your manuscript has entered into Pre-accept status. Now please check and complete the necessary information of your manuscript for final publication.', step1: 'Article Processing Charge', @@ -1941,6 +2118,16 @@ const en = { briefNeedDiff: 'Clarify', briefDelete: 'Drop', briefYearMismatch: 'Year?' + }, + reviewHistory: { + certificateTitle: 'Certificate', + exportCertificateImage: 'Download Image', + generateCertificatePdf: 'Generate PDF', + exportCertificateImageSuccess: 'Certificate image exported', + exportCertificateImageFail: 'Failed to export certificate image', + generateCertificatePdfSuccess: 'Certificate PDF generated', + generateCertificatePdfFail: 'Failed to generate certificate PDF', + noCertificate: 'No certificate' } diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js index f6a89ba..4d28526 100644 --- a/src/components/common/langs/zh.js +++ b/src/components/common/langs/zh.js @@ -79,6 +79,7 @@ const zh = { ArticlePublicationanalysis: '文章发表分析', journalArticleCount: '文章计数', + journalFeeApproval: '期刊收费审批', Promotionsystem: '推广管理', Userdatabase: '用户数据库', analysis: '文章分析', @@ -1197,6 +1198,112 @@ const zh = { citeRelevanceCopyGroup: '复制本段', citeRelevanceDownloadHtml: '下载 HTML', citeRelevanceDownloadHtmlSuccess: 'HTML 已下载', + citeRelevanceWithUploadedRefs: '将使用已上传的 {n} 条参考文献进行分组导出', + citeRelevanceDownloadHtmlSuccessWithUploaded: 'HTML 已下载(基于已上传的 {n} 条参考文献)', + refAnnotationReportTip: '下载带批注的 HTML 相关性分析报告(需先完成 AI 检测)', + refAnnotationReportShort: '批注报告', + exportRelevanceWordTip: '导出带引文相关性批注的 Word(需先完成 AI 检测;黄/红项原因写入批注)', + exportRelevanceWordShort: '相关性Word', + exportRelevanceWordSuccess: '带相关性批注的 Word 已下载', + exportRelevanceWordFail: '带相关性批注的 Word 导出失败', + mediaRefCheckWarn: '图表引用校对:正文引用了未排版的 {list}', + mediaRefCheckMore: ' 等共 {n} 处', + wordRefsPreviewShort: 'Word 参考文献', + wordRefsPreviewTitle: '原 Word 稿件 · 参考文献', + wordRefsPreviewTotal: '共 {count} 条参考文献', + wordRefsPreviewClose: '关闭', + wordRefsPreviewCopy: '复制纯文本', + wordRefsPreviewCopySuccess: '参考文献内容已复制', + wordRefsPreviewCopyFail: '复制失败', + wordRefsPreviewNoArticle: '缺少稿件 ID,无法读取 Word 正文', + wordRefsPreviewNoManuscript: '未找到稿件 Word 文件(gridData / getFilesForArticle)', + wordRefsPreviewEmpty: '未在原 Word 稿件中识别到 References / 参考文献 段落', + wordRefsPreviewFail: '读取 Word 参考文献失败', + wordRefBatchUploadShort: '上传校对 Word', + wordRefBatchUploadTip: '上传校对改好的 Word 参考文献,按序号拆解字段并批量保存到系统', + wordRefBatchDialogTitle: 'Word 参考文献 · 字段对照', + wordRefBatchApply: '应用', + wordRefBatchStatus: '状态', + wordRefBatchType: '类型', + wordRefBatchAuthor: '作者', + wordRefBatchTitle: '标题', + wordRefBatchJournal: '期刊', + wordRefBatchDateno: '出版信息', + wordRefBatchDoi: 'DOI/链接', + wordRefBatchExisting: '系统现有', + wordRefBatchMatched: '已对应', + wordRefBatchMissing: 'Word 缺失', + wordRefBatchExtra: '系统无此项', + wordRefBatchSelectAll: '全选', + wordRefBatchSelectNone: '全不选', + wordRefBatchReparse: '服务端精拆字段', + wordRefBatchReparseSuccess: '字段已重新拆解', + wordRefBatchReparseFail: '字段拆解失败', + wordRefBatchSave: '保存选中项', + wordRefBatchSaveSuccess: '已保存 {ok} 条,失败 {fail} 条', + wordRefBatchSaveFail: '批量保存失败', + wordRefBatchNothingSelected: '请至少勾选一条可保存的参考文献', + wordRefBatchParseFail: 'Word 参考文献解析失败', + wordRefBatchCountMismatch: '条数不一致:系统 {existing} 条,Word {uploaded} 条,请核对后再保存', + wordRefBatchClickToEdit: '点击文字即可修改', + wordRefBatchFieldEmpty: '点击填写', + wordRefBatchShowExisting: '对比系统', + wordRefBatchHideExisting: '收起', + wordRefBatchParseFailed: '拆解不完整,请逐项填写', + wordRefBatchWordOriginal: 'Word 原文', + refAnnotationReportSuccess: '批注分析报告 HTML 已下载', + refAnnotationReportFail: '批注分析报告下载失败', + refAnnotationReportNoArticle: '缺少稿件 ID,无法获取 AI 检测数据', + refAnnotationReportNoData: '暂无 AI 相关性检测结果,请先在参考文献转换页完成检测', + refAnnotationReportTitleSuffix: '审稿报告', + refAnnotationReportManuscriptPreview: 'word 原稿预览:{id}', + refAnnotationReportRefMatchPrefix: '文献匹配依据:', + refAnnotationReportRefLibraryPrefix: '匹配文献库:', + refAnnotationReportRefTotal: '共 {n} 条', + refAnnotationReportAnalysisTitle: '文献深度分析', + refAnnotationReportStatTotal: '已审文献数', + refAnnotationReportStatDirect: '直接相关', + refAnnotationReportStatStrong: '强相关', + refAnnotationReportStatWeak: '弱相关', + refAnnotationReportStatNone: '不相关', + refAnnotationReportLocateTip: '点击定位并在左侧高亮原文段落', + refAnnotationReportLocatePara: '对应左侧段落 {n} (点击定位)', + refAnnotationReportParaRefsPrefix: '本段引用:', + refAnnotationReportVerdictLabel: '判定:', + refAnnotationReportReasonLabel: '理由:', + refAnnotationReportAuthorCommentLabel: '编辑批注:', + refAnnotationReportNoReason: '暂无说明', + refAnnotationReportConclusionLabel: '校对结论:', + refAnnotationReportConclusionKeep: '可保留', + refAnnotationReportConclusionRevise: '建议修改', + refAnnotationReportNoConclusion: '暂无结论', + refAnnotationReportScoreLabel: '相关性:{score}%', + refAnnotationReportCiteLocLabel: '引用处 {n}', + refAnnotationReportCiteParaSuffix: ' · 段落 {para}', + refAnnotationReportParaSectionTitle: '按正文段落', + refAnnotationReportRefConclusionSectionTitle: '参考文献 · 全部校对结论', + refAnnotationReportRefCiteTotal: '共 {n} 处引用', + refAnnotationReportSummaryTitle: 'AI批注总结:', + refAnnotationReportSummaryPrefix: '以下文献相关性较弱,建议核实:', + refAnnotationReportSummaryRefPrefix: '文献', + refAnnotationReportWarnBadge: '发现弱/不相关文献', + refAnnotationReportIssueNavTitle: '需关注段落', + refAnnotationReportIssueNavHint: '点击段落号快速跳转到右侧批注意见', + refAnnotationReportIssueCountLabel: '{n} 处建议修改', + refAnnotationReportJumpToReview: '查看批注 →', + refAnnotationReportJumpToReviewTip: '跳转到右侧该段批注意见', + refAnnotationReportLocateSource: '↩ 定位原文', + refAnnotationReportParaBadge: '段落 {n}', + refAnnotationReportParaOkLabel: '引用正常', + refAnnotationReportLegendIssue: '红色边框:该段有建议修改的引用', + refAnnotationReportLegendOk: '正常段落:引用已检测且可保留', + refAnnotationReportLegendCite: '点击引用编号或「查看批注」跳转右侧意见', + refAnnotationReportNoData: '暂无 AI 相关性检测结果,请先在参考文献转换页完成检测。', + refAnnotationReportDefaultTitle: 'manuscript', + refAnnotationReportLevelDirect: '直接相关', + refAnnotationReportLevelStrong: '强相关', + refAnnotationReportLevelWeak: '弱相关', + refAnnotationReportLevelNone: '不相关', refHtmlCopy: '复制参考文献', refHtmlCopyShort: '复制', refHtmlCopySuccess: '参考文献已复制(含编号、期刊斜体、蓝色 DOI、Available at 换行)', @@ -1237,6 +1344,10 @@ const zh = { refHtmlSaveTip: '提示:整段编辑后点击上方按钮保存 HTML;也可复制内容后在排版页「粘贴参考文献」导出 Word。', refHtmlDownloadEdited: '下载编辑后的 HTML', refHtmlDownloadFileName: 'references-edited.html', + refHtmlCopyBracket: '复制 [编号] 格式', + refHtmlCopyBracketSuccess: '已复制到剪贴板([n] 编号,Available at: 后换行)', + refHtmlCopyBracketEmpty: '编辑框中没有可复制的内容', + refHtmlCopyBracketFail: '复制失败,请手动选择复制', exportImg: '导出 图片', PaperRotation: '纸张方向', removeAnnotations: '确定要删除这条批注吗?', @@ -1329,6 +1440,7 @@ const zh = { youhui: '折扣价', discountprice: '折扣价', youhuiremark: '折扣说明', + applyPrice: '申请', submitOrder: '付款', state0: '待付款', state1: '已缴费', @@ -1342,6 +1454,71 @@ const zh = { Paymentstatus: '缴费状态', time: '缴费时间', }, + journalFeeApproval: { + title: '期刊收费审批', + pendingList: '待审批改价申请', + pageDesc: '以下为编辑提交的稿件处理费改价申请。您可按文章标题或 SN 号搜索,核对原金额、申请金额及改价说明后,选择同意或驳回。', + searchLabel: '搜索', + colArticleInfo: '文章信息', + colPriceChange: '改价详情', + colApproval: '审批操作', + historyTitle: '申请历史', + no: '序号', + articleId: '文章 ID', + journal: '期刊名称', + articleTitle: '文章标题', + sn: 'SN 号', + originalFee: '原处理费 (USD)', + applyFee: '申请改价 (USD)', + remark: '改价说明', + status: '审批状态', + operator: '申请人', + applyTime: '申请时间', + action: '审批操作', + accept: '同意', + reject: '驳回', + cancel: '取消', + tip: '提示', + acceptConfirm: '确认同意该改价申请?', + rejectTitle: '驳回申请', + rejectRemark: '驳回备注', + rejectRemarkPlaceholder: '请填写驳回原因', + rejectRemarkRequired: '请填写驳回备注', + noPending: '暂无待审批申请', + noAccepted: '暂无已通过申请', + noRejected: '暂无已驳回申请', + tabPending: '待审批', + tabAccepted: '已通过', + tabRejected: '已驳回', + noHistory: '暂无申请历史', + viewHistory: '申请历史', + refresh: '刷新', + search: '搜索', + searchPlaceholder: '输入文章标题或 SN 号', + noSearchResult: '未找到匹配的待审批申请', + currentArticle: '当前文章', + selectArticleHint: '点击待审批列表中的文章,或输入文章 ID 查询申请历史', + articleIdPlaceholder: '文章 ID', + searchHistory: '查询历史', + articleIdRequired: '请输入文章 ID', + missingArticleId: '该记录缺少 article_id,无法查询历史', + historyLoadFailed: '加载申请历史失败', + feeChange: '金额变更', + loadFailed: '加载失败,请稍后重试', + actionFailed: '操作失败,请稍后重试', + acceptSuccess: '已同意该申请', + rejectSuccess: '已驳回该申请', + noPermission: '无权限访问该页面', + statusApply: '提交申请', + statusAccept: '审批通过', + statusReject: '审批驳回', + statusPending: '待审批', + statusOther: '其他记录', + viewArticleDetail: '点击查看文章详情', + notifyTitle: '期刊收费审批', + notifyMessage: '您有 {count} 条待审批改价申请', + notifyHint: '点击前往审批页面处理', + }, PreAccept: { successInfo: 'Congratulations! Your manuscript has entered into Pre-accept status. Now please check and complete the necessary information of your manuscript for final publication.', step1: '文章处理费用', @@ -1919,6 +2096,16 @@ const zh = { briefNeedDiff: '需说明差异', briefDelete: '删除', briefYearMismatch: '年份不符' + }, + reviewHistory: { + certificateTitle: '证书', + exportCertificateImage: '下载图片', + generateCertificatePdf: '生成 PDF', + exportCertificateImageSuccess: '证书图片已导出', + exportCertificateImageFail: '证书图片导出失败', + generateCertificatePdfSuccess: '证书 PDF 已生成', + generateCertificatePdfFail: '证书 PDF 生成失败', + noCertificate: '暂无证书' } diff --git a/src/components/page/PreIngestedEditor.vue b/src/components/page/PreIngestedEditor.vue index efced6b..93bdc1b 100644 --- a/src/components/page/PreIngestedEditor.vue +++ b/src/components/page/PreIngestedEditor.vue @@ -115,6 +115,64 @@ +
+
{{ $t('journalFeeApproval.historyTitle') }}
+
+
+
+ + +
+
+ + {{ item.statusText }} + + + {{ formatApplyDateTime(item.applyTime) }} + + + {{ $t('pendingPayment.total') }}: {{ formatApplyFee(item.originalFee) }} USD + + | + + {{ $t('pendingPayment.discountprice') }}: {{ formatApplyFee(item.applyFee) }} USD + + + + +
+
+
+
@@ -256,7 +314,7 @@ Cancel - Save + {{ $t('pendingPayment.applyPrice') }} @@ -281,6 +339,8 @@ export default { tableData: [], talkMsgs: [], finalFeeVisible: false, + priceApplyHistoryLoading: false, + priceApplyHistory: [], FeeVisible: false, communVisible: false, msgform: { @@ -581,6 +641,7 @@ export default { created() { this.opMedical = this.$commonJS.opMedicalList(); this.getPreacceptPayment(); + this.fetchArticlePriceApplyHistory(); this.getHight(); window.addEventListener('resize', this.getHight); // this.getData(); @@ -591,6 +652,7 @@ export default { activated() { this.opMedical = this.$commonJS.opMedicalList(); this.getPreacceptPayment(); + this.fetchArticlePriceApplyHistory(); this.getHight(); window.addEventListener('resize', this.getHight); // this.getData(); @@ -614,11 +676,10 @@ export default { background: 'rgba(0, 0, 0, 0.7)' }); this.$api - .post('api/Order/changePrice', { + .post('api/Order/applyPrice', { article_id: this.$route.query.id, - editor_id: localStorage.getItem('U_id'), fee: Number(this.finalFeeData.fee).toFixed(2), - fee_remark: this.finalFeeData.fee_remark + remark: this.finalFeeData.fee_remark }) .then((res) => { load.close(); @@ -626,6 +687,7 @@ export default { if (res.code == 0) { this.finalFeeVisible = false; this.getPreacceptPayment(); + this.fetchArticlePriceApplyHistory(); } else { this.$message.error(res.msg); } @@ -646,6 +708,106 @@ export default { fee_remark: this.article_pay_info.fee_remark }; }, + fetchArticlePriceApplyHistory() { + const articleId = this.$route.query.id; + if (!articleId) return; + this.priceApplyHistoryLoading = true; + this.$api + .post('api/Order/getArticlePriceApplyList', { + article_id: articleId + }) + .then((res) => { + this.priceApplyHistoryLoading = false; + if (res.code == 0) { + this.priceApplyHistory = this.normalizePriceApplyHistory(res.data); + } else { + this.priceApplyHistory = []; + } + }) + .catch(() => { + this.priceApplyHistoryLoading = false; + this.priceApplyHistory = []; + }); + }, + extractPriceApplyList(data) { + if (Array.isArray(data)) return data; + if (!data || typeof data !== 'object') return []; + return data.list || data.applies || data.applys || data.items || data.logs || data.data || []; + }, + pickApplyValue() { + for (let i = 0; i < arguments.length; i++) { + const value = arguments[i]; + if (value !== undefined && value !== null) { + return value; + } + } + return ''; + }, + normalizePriceApplyHistory(data) { + const rawList = this.extractPriceApplyList(data); + return rawList + .map((row) => { + if (!row || typeof row !== 'object') return null; + const status = row.status !== undefined ? row.status : row.state; + return { + originalFee: this.pickApplyValue(row.old_fee, row.original_fee, row.origin_fee, row.original_price, row.total, ''), + applyFee: this.pickApplyValue(row.fee, row.apply_fee, row.new_fee, ''), + remark: this.pickApplyValue(row.remark, row.fee_remark, row.apply_remark, ''), + rejectRemark: this.pickApplyValue(row.reject_remark, row.reject_reason, row.audit_remark, ''), + operator: row.operator || row.account || row.editor_name || row.apply_user || row.username || row.realname || '-', + applyTime: row.ctime || row.apply_time || row.create_time || row.add_time || row.update_time || row.time || 0, + statusText: this.getApplyStatusText(status, row) + }; + }) + .filter(Boolean) + .sort((a, b) => Number(b.applyTime) - Number(a.applyTime)); + }, + getApplyStatusText(status, row) { + const raw = status !== undefined && status !== null && status !== '' ? status : row.action || row.type || ''; + const text = String(raw).toLowerCase(); + if (text.includes('reject') || text === '2' || text === '拒绝' || text === '驳回') { + return this.$t('journalFeeApproval.statusReject'); + } + if (text.includes('accept') || text.includes('agree') || text === '1' || text === '同意' || text === '通过') { + return this.$t('journalFeeApproval.statusAccept'); + } + if (text.includes('pending') || text.includes('wait') || text === '0' || text === '待审' || text === '待审批') { + return this.$t('journalFeeApproval.statusPending'); + } + if (text.includes('apply') || text === '申请' || text === '提交') { + return this.$t('journalFeeApproval.statusApply'); + } + return raw ? String(raw) : this.$t('journalFeeApproval.statusOther'); + }, + applyTimelineType(statusText) { + if (statusText === this.$t('journalFeeApproval.statusAccept')) return 'success'; + if (statusText === this.$t('journalFeeApproval.statusReject')) return 'danger'; + if (statusText === this.$t('journalFeeApproval.statusPending')) return 'warning'; + return 'primary'; + }, + applyTagType(statusText) { + if (statusText === this.$t('journalFeeApproval.statusAccept')) return 'success'; + if (statusText === this.$t('journalFeeApproval.statusReject')) return 'danger'; + if (statusText === this.$t('journalFeeApproval.statusPending')) return 'warning'; + return ''; + }, + formatApplyFee(value) { + if (value === '' || value === null || value === undefined) return '-'; + const num = Number(value); + if (isNaN(num)) return value; + return num.toFixed(2); + }, + formatApplyDateTime(timestamp) { + if (!timestamp) return '-'; + if (typeof timestamp === 'string' && timestamp.indexOf('-') > 0) { + return timestamp; + } + const ts = String(timestamp).length === 10 ? Number(timestamp) * 1000 : Number(timestamp); + const date = new Date(ts); + if (isNaN(date.getTime())) return '-'; + const pad = (n) => (n < 10 ? '0' + n : '' + n); + return date.getFullYear() + '-' + pad(date.getMonth() + 1) + '-' + pad(date.getDate()) + ' ' + pad(date.getHours()) + ':' + pad(date.getMinutes()) + ':' + pad(date.getSeconds()); + }, formatAmount(amount) { return amount.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); @@ -2112,6 +2274,99 @@ export default { .payment_info .price { font-weight: 700; } +.price-apply-history { + margin-top: 18px; + padding-top: 16px; + border-top: 1px solid #ebeef5; +} +.price-apply-history__title { + margin: 0 0 12px; + font-size: 15px; + color: #303133; + font-weight: 600; +} +.price-apply-history__list { + padding: 0; +} +.price-apply-history__item { + display: flex; + align-items: flex-start; +} +.price-apply-history__item + .price-apply-history__item { + margin-top: 10px; +} +.price-apply-history__track { + position: relative; + width: 14px; + flex-shrink: 0; + margin-right: 12px; + align-self: stretch; +} +.price-apply-history__dot { + display: block; + width: 10px; + height: 10px; + border-radius: 50%; + margin: 4px auto 0; + background: #409eff; +} +.price-apply-history__dot.is-success { + background: #67c23a; +} +.price-apply-history__dot.is-danger { + background: #f56c6c; +} +.price-apply-history__dot.is-warning { + background: #e6a23c; +} +.price-apply-history__dot.is-primary { + background: #409eff; +} +.price-apply-history__dot.is-info { + background: #909399; +} +.price-apply-history__tail { + position: absolute; + left: 50%; + top: 16px; + bottom: -10px; + width: 2px; + margin-left: -1px; + background: #e4e7ed; +} +.price-apply-history__row { + flex: 1; + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 8px; + min-width: 0; + padding-top: 1px; + line-height: 1.6; +} +.price-apply-history__tag { + flex-shrink: 0; +} +.price-apply-history__time { + flex-shrink: 0; + color: #909399; + font-size: 12px; + white-space: nowrap; +} +.price-apply-history__text { + color: #606266; + font-size: 13px; +} +.price-apply-history__sep { + color: #dcdfe6; + font-size: 12px; + line-height: 1; +} +.price-apply-history__empty { + margin: 0; + color: #909399; + font-size: 13px; +} .payment_info .remark { /* color: #888; */ } diff --git a/src/components/page/PreIngestedEditorProduce.vue b/src/components/page/PreIngestedEditorProduce.vue index 7b556ee..f8bc18a 100644 --- a/src/components/page/PreIngestedEditorProduce.vue +++ b/src/components/page/PreIngestedEditorProduce.vue @@ -770,6 +770,12 @@ export default { this.$refs.editPublicRefRdit.$forceUpdate(); console.log('editPublicRefRdit'); }, + grabReferencesFromWord() { + if (this.$refs.editPublicRefRdit && this.$refs.editPublicRefRdit.handleGrabReferencesFromWord) { + return this.$refs.editPublicRefRdit.handleGrabReferencesFromWord(); + } + return Promise.resolve(); + }, // 跳转邮件 linkEmail() { this.$router.push({ diff --git a/src/components/page/Register.vue b/src/components/page/Register.vue index 5c75ee6..3501f8a 100644 --- a/src/components/page/Register.vue +++ b/src/components/page/Register.vue @@ -4,7 +4,7 @@
User Register
- + @@ -18,7 +18,7 @@ - + diff --git a/src/components/page/articleListEditor_B1.vue b/src/components/page/articleListEditor_B1.vue index 88ed8dd..d59c027 100644 --- a/src/components/page/articleListEditor_B1.vue +++ b/src/components/page/articleListEditor_B1.vue @@ -1,13 +1,26 @@ @@ -1183,9 +1215,15 @@ import { getAuthorAffiliationPreview } from '@/utils/productionSubmissionImport'; import { isZyModeEnabled, isZySkipCheckEnabled } from '@/utils/zyMode'; +import { fetchWordReferencesSection } from '@/utils/manuscriptWordReferences'; export default { data() { return { + wordRefsDialogVisible: false, + wordRefsLoading: false, + wordRefsHtml: '', + wordRefsPlainText: '', + wordRefsCount: 0, importingSubmission: false, importAuthorPreviewVisible: false, importAuthorOrderText: '', @@ -1759,6 +1797,66 @@ export default { } }); }, + async handleShowWordReferences() { + if (this.wordRefsLoading) return; + const manuscriptPath = String(this.gridData || '').trim(); + if (!manuscriptPath) { + this.$message.warning(this.$t('commonTable.wordRefsPreviewNoManuscript')); + return; + } + this.wordRefsLoading = true; + try { + const extracted = await fetchWordReferencesSection(this.$api, { + manuscriptPath: manuscriptPath, + gridData: manuscriptPath, + mediaUrl: this.mediaUrl, + baseUrl: this.baseUrl, + useGridDataOnly: true + }); + if (!extracted.found) { + this.$message.warning(this.$t('commonTable.wordRefsPreviewEmpty')); + return; + } + this.wordRefsHtml = extracted.html; + this.wordRefsPlainText = extracted.plainText || ''; + this.wordRefsCount = extracted.count || 0; + this.wordRefsDialogVisible = true; + } catch (err) { + console.error(err); + if (err && err.message === 'NO_MANUSCRIPT_FILE') { + this.$message.warning(this.$t('commonTable.wordRefsPreviewNoManuscript')); + } else { + this.$message.error(this.$t('commonTable.wordRefsPreviewFail')); + } + } finally { + this.wordRefsLoading = false; + } + }, + async copyWordRefsPlainText() { + const text = String(this.wordRefsPlainText || '').trim(); + if (!text) { + this.$message.warning(this.$t('commonTable.wordRefsPreviewEmpty')); + return; + } + try { + if (navigator.clipboard && navigator.clipboard.writeText) { + await navigator.clipboard.writeText(text); + } else { + const ta = document.createElement('textarea'); + ta.value = text; + ta.style.position = 'fixed'; + ta.style.left = '-9999px'; + document.body.appendChild(ta); + ta.select(); + document.execCommand('copy'); + document.body.removeChild(ta); + } + this.$message.success(this.$t('commonTable.wordRefsPreviewCopySuccess')); + } catch (err) { + console.error(err); + this.$message.error(this.$t('commonTable.wordRefsPreviewCopyFail')); + } + }, allLoad() { let excelList = [ 'https://submission.tmrjournals.com/public/manuscirpt/20220831/c7d75d49bf25cf56906d56d07e00a31e.docx', @@ -1914,7 +2012,12 @@ export default { type: 'm' }) .then((res) => { - this.gridData = res.data.files[res.data.files.length - 1].file_url; + const files = res.data && Array.isArray(res.data.files) ? res.data.files : []; + if (files.length > 0) { + this.gridData = files[files.length - 1].file_url || ''; + } else { + this.gridData = ''; + } }) .catch((err) => { console.log(err); @@ -3599,6 +3702,13 @@ export default { computed: { zyModeEnabled() { return isZyModeEnabled(this.$route); + }, + wordRefsDialogTitle() { + const base = this.$t('commonTable.wordRefsPreviewTitle'); + if (this.wordRefsCount > 0) { + return base + ' (' + this.wordRefsCount + ')'; + } + return base; } }, components: { @@ -3699,6 +3809,73 @@ export default { z-index: 50; } +.tab-post-word-refs-btn, +.tab-post-detail-ms-btn { + padding: 6px; + margin-left: 0; + position: absolute; + left: 0; + width: 220px; +} + +.tab-post-word-refs-btn { + bottom: 58px; +} + +.tab-post-detail-ms-btn { + bottom: 20px; +} + +::v-deep .word-refs-preview-dialog .el-dialog__body { + padding-top: 10px; +} + +.word-refs-preview-meta { + margin-bottom: 10px; + padding: 8px 12px; + background: #f4f8ff; + border: 1px solid #d9e8ff; + border-radius: 6px; + font-size: 13px; + color: #409eff; + font-weight: 600; +} + +.word-refs-preview-body { + max-height: 68vh; + overflow: auto; + padding: 12px 14px; + background: #fff; + border: 1px solid #ebeef5; + border-radius: 6px; + line-height: 1.65; + font-size: 14px; + color: #303133; +} + +.word-refs-preview-body .word-ref-block { + margin-bottom: 8px; +} + +.word-refs-preview-body .word-ref-h1 { + font-weight: 700; + text-align: center; + margin: 8px 0 12px; +} + +.word-refs-preview-body .word-ref-h2, +.word-refs-preview-body .word-ref-h3 { + font-weight: 700; +} + +.word-refs-preview-body .word-ref-p p { + margin: 0 0 8px; +} + +.word-refs-preview-body blue { + color: #006699; +} + .tab_post > div { margin-bottom: 10px; cursor: pointer; diff --git a/src/components/page/autoPromotionLogs.vue b/src/components/page/autoPromotionLogs.vue index 8b27c46..52fa85b 100644 --- a/src/components/page/autoPromotionLogs.vue +++ b/src/components/page/autoPromotionLogs.vue @@ -114,6 +114,42 @@ +
+
+ {{ $t('autoPromotionLogs.monthlyStatsTitle') }} + +
+
+
+
{{ $t('autoPromotionLogs.monthlyStatsTaskCount') }}
+
{{ zyMonthlyStats.taskCount }}
+
+
+
{{ $t('autoPromotionLogs.totalCount') }}
+
{{ zyMonthlyStats.total }}
+
+
+
{{ $t('autoPromotionLogs.sentCount') }}
+
{{ zyMonthlyStats.sent }}
+
+
+
{{ $t('autoPromotionLogs.failCount') }}
+
{{ zyMonthlyStats.fail }}
+
+
+
{{ $t('autoPromotionLogs.bounceCount') }}
+
{{ zyMonthlyStats.bounce }}
+
+
+
+ { - const runAt = item.run_at || item.run_time || item.plan_time || item.execute_time || item.send_date || ''; - const state = String(item.state != null ? item.state : ''); - const promotionFactoryId = - item.promotion_factory_id != null ? item.promotion_factory_id : item.id != null ? item.id : item.task_id; - return { - id: item.id || item.task_id || `task_${idx + 1}`, - promotion_factory_id: promotionFactoryId != null ? String(promotionFactoryId) : '', - task_id: String(item.task_id != null ? item.task_id : item.id || ''), - task_name: item.task_name || item.name || '', - scene: item.scene || '', - name: item.name || item.expert_name || '', - email: item.email || item.to_email || '', - template_id: String(item.template_id || item.default_template_id || ''), - style_id: String(item.style_id || item.default_style_id || ''), - style_name: item.style_name || item.default_style_name || '', - state: state, - paused: state === '2', - run_at: runAt, - total_count: Number(item.total_count || 0), - sent_count: Number(item.sent_count || 0), - fail_count: Number(item.fail_count || 0), - bounce_count: Number(item.bounce_count || 0), - min_interval: Number(item.min_interval || 0), - max_interval: Number(item.max_interval || 0), - send_start_hour: item.send_start_hour != null ? item.send_start_hour : '-', - send_end_hour: item.send_end_hour != null ? item.send_end_hour : '-' - }; - }); + this.list = rawList.map((item, idx) => this.mapTaskListItem(item, idx)); this.total = Number(data.total || data.count || this.list.length || 0); // 兼容后端返回 page/per_page @@ -1240,6 +1266,113 @@ export default { handleSearch() { this.query.pageIndex = 1; this.fetchList(); + this.fetchZyMonthlyStats(); + }, + extractTaskRunYearMonth(runAt) { + const raw = String(runAt || '').trim(); + if (!raw) return ''; + const match = raw.match(/^(\d{4})-(\d{2})/); + if (match) { + return match[1] + '-' + match[2]; + } + const date = new Date(raw); + if (isNaN(date.getTime())) return ''; + return date.getFullYear() + '-' + String(date.getMonth() + 1).padStart(2, '0'); + }, + mapTaskListItem(item, idx) { + const runAt = item.run_at || item.run_time || item.plan_time || item.execute_time || item.send_date || ''; + const state = String(item.state != null ? item.state : ''); + const promotionFactoryId = + item.promotion_factory_id != null ? item.promotion_factory_id : item.id != null ? item.id : item.task_id; + return { + id: item.id || item.task_id || `task_${idx + 1}`, + promotion_factory_id: promotionFactoryId != null ? String(promotionFactoryId) : '', + task_id: String(item.task_id != null ? item.task_id : item.id || ''), + task_name: item.task_name || item.name || '', + scene: item.scene || '', + name: item.name || item.expert_name || '', + email: item.email || item.to_email || '', + template_id: String(item.template_id || item.default_template_id || ''), + style_id: String(item.style_id || item.default_style_id || ''), + style_name: item.style_name || item.default_style_name || '', + state: state, + paused: state === '2', + run_at: runAt, + total_count: Number(item.total_count || 0), + sent_count: Number(item.sent_count || 0), + fail_count: Number(item.fail_count || 0), + bounce_count: Number(item.bounce_count || 0), + min_interval: Number(item.min_interval || 0), + max_interval: Number(item.max_interval || 0), + send_start_hour: item.send_start_hour != null ? item.send_start_hour : '-', + send_end_hour: item.send_end_hour != null ? item.send_end_hour : '-' + }; + }, + async fetchAllTaskRowsForStats() { + const rows = []; + let page = 1; + const perPage = 200; + let total = 0; + do { + const params = { + journal_id: String(this.selectedJournalId || ''), + factory_id: String(this.routePromotionFactoryId || ''), + page: page, + per_page: perPage + }; + const res = await this.$api.post(API.list, params); + const data = (res && res.data) || {}; + const rawList = Array.isArray(data.list) ? data.list : []; + rows.push.apply(rows, rawList); + total = Number(data.total || data.count || rows.length || 0); + if (!rawList.length || rows.length >= total) { + break; + } + page += 1; + } while (page <= 100); + return rows; + }, + async fetchZyMonthlyStats() { + if (!this.zyModeEnabled || !this.selectedJournalId) { + return; + } + const month = String(this.zyStatsMonth || '').trim(); + if (!month) { + return; + } + this.zyStatsLoading = true; + try { + const rawRows = await this.fetchAllTaskRowsForStats(); + const stats = { + taskCount: 0, + total: 0, + sent: 0, + fail: 0, + bounce: 0 + }; + rawRows.forEach(function (item, idx) { + const mapped = this.mapTaskListItem(item, idx); + if (this.extractTaskRunYearMonth(mapped.run_at) !== month) { + return; + } + stats.taskCount += 1; + stats.total += mapped.total_count; + stats.sent += mapped.sent_count; + stats.fail += mapped.fail_count; + stats.bounce += mapped.bounce_count; + }, this); + this.zyMonthlyStats = stats; + } catch (e) { + this.zyMonthlyStats = { + taskCount: 0, + total: 0, + sent: 0, + fail: 0, + bounce: 0 + }; + } finally { + this.zyStatsLoading = false; + } }, handleStateChange() { this.query.pageIndex = 1; @@ -1723,6 +1856,27 @@ export default { .delete-btn:hover { color: #be123c !important; } +.zy-monthly-stats-bar { + margin: 12px 0 14px; + padding: 12px 14px; + background: #fff; + border: 1px solid #e2e8f0; + border-radius: 8px; +} +.zy-monthly-stats-head { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 10px; +} +.zy-monthly-stats-title { + font-size: 14px; + font-weight: 600; + color: #334155; +} +.zy-monthly-stats-grid { + margin-bottom: 0; +} .delivery-dashboard { padding: 0px 0; } diff --git a/src/components/page/components/table/word.vue b/src/components/page/components/table/word.vue index a4914af..d2a48ed 100644 --- a/src/components/page/components/table/word.vue +++ b/src/components/page/components/table/word.vue @@ -140,8 +140,12 @@ v-if="zyModeEnabled" @click="handleOpenCitationRelevance" class="zy-toolbar-btn" + :class="{ 'zy-toolbar-btn--ready': uploadedReferencesCount }" > - + +
  • + + + + + {{ $t('commonTable.exportRelevanceWordShort') }} + + +
  • 3 + ? this.$t('commonTable.mediaRefCheckMore', { n: mediaRefIssues.length }) + : ''; + this.$message.warning(this.$t('commonTable.mediaRefCheckWarn', { list: preview + suffix })); + } this.$api .post('api/Proofread/modify', { diff --git a/src/components/page/editPublicRefRdit.vue b/src/components/page/editPublicRefRdit.vue index 7734f6e..da1be17 100644 --- a/src/components/page/editPublicRefRdit.vue +++ b/src/components/page/editPublicRefRdit.vue @@ -117,6 +117,46 @@ Delete + + + {{ $t('commonTable.refAnnotationReportShort') }} + + + + + {{ $t('commonTable.wordRefBatchUploadShort') }} + + +

    - {{ scope.row.author }} .  {{ scope.row.joura }} {{ scope.row.joura }}{{ scope.row.dateno }}.

    @@ -769,8 +809,7 @@ {{ relevanceReviewRefRow.author }} {{ relevanceReviewRefRow.joura }} {{ relevanceReviewRefRow.joura }}{{ relevanceReviewRefRow.dateno }}.
    + + +
    + {{ $t('commonTable.wordRefBatchCountMismatch', { existing: chanFerForm.length, uploaded: wordRefBatchUploadedCount }) }} +
    +
    + {{ $t('commonTable.wordRefBatchClickToEdit') }} + {{ $t('commonTable.wordRefBatchSelectAll') }} + {{ $t('commonTable.wordRefBatchSelectNone') }} + + {{ $t('commonTable.wordRefBatchReparse') }} + +
    +
    +
    +
    + {{ row.index }}. + + + + + + + + {{ $t('commonTable.wordRefBatchParseFailed') }} + + + {{ wordRefBatchExistingExpanded[row.index] ? $t('commonTable.wordRefBatchHideExisting') : $t('commonTable.wordRefBatchShowExisting') }} + +
    + +
    +
    + {{ $t('commonTable.wordRefBatchWordOriginal') }}: + {{ wordRefBatchWordPreview(row) }} +
    +
    + + + +
    +
    + +
    +
    + + +
    +
    + +
    + + + {{ + row.author || $t('commonTable.wordRefBatchFieldEmpty') + }} + + + + + {{ + row.title || $t('commonTable.wordRefBatchFieldEmpty') + }} + + . + + + {{ + row.dateno || $t('commonTable.wordRefBatchFieldEmpty') + }} + + Available at: ISBN: + + + {{ + row.isbn || $t('commonTable.wordRefBatchFieldEmpty') + }} + +
    + +
    + + + {{ + row.author || $t('commonTable.wordRefBatchFieldEmpty') + }} + + + + + {{ + row.title || $t('commonTable.wordRefBatchFieldEmpty') + }} + + {{ wordRefBatchSepAfterTitle(row) }} + + + {{ + row.joura || $t('commonTable.wordRefBatchFieldEmpty') + }} + + {{ wordRefBatchSepAfterJoura(row) }} + + + {{ + row.dateno || $t('commonTable.wordRefBatchFieldEmpty') + }} + + . Available at: + + + {{ + row.doilink || $t('commonTable.wordRefBatchFieldEmpty') + }} + +
    + +
    + {{ $t('commonTable.wordRefBatchExisting') }}: + {{ row.existingPreview }} +
    +
    +
    + + {{ $t('commonTable.wordRefsPreviewClose') }} + + {{ $t('commonTable.wordRefBatchSave') }} + + +
    @@ -910,6 +1215,23 @@ 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'; +import { + buildReferenceAnnotationReportLabels, + downloadReferenceAnnotationReportHtml, + fetchReferenceCheckProgress, + flattenProgressRecords +} from '@/utils/manuscriptReferenceAnnotationReport'; +import { + buildWordRefBatchRows, + applyWordRefBatchOtherType, + enrichWordRefBatchRowsWithDealContent, + getWordRefBatchFieldKeys, + isWordRefBatchRowParseIncomplete, + parseLocalWordReferencesFile, + parseManuscriptWordReferences, + saveWordRefBatchRows, + stripWordRefBatchHtmlPreview +} from '@/utils/wordReferenceBatchImport'; /** 单条引用 records[].status:0 待检测 2 已完成 3 检测失败 */ const REF_RELEVANCE_RECORD_STATUS_PENDING = 0; @@ -1080,13 +1402,32 @@ export default { refRelevanceRedetectDialogVisible: false, refRelevanceRedetectChoice: 'all', /** 重新核查入口(暂隐藏) */ - showRefRelevanceRedetectButton: false + showRefRelevanceRedetectButton: false, + annotationReportLoading: false, + wordRefBatchDialogVisible: false, + wordRefBatchLoading: false, + wordRefBatchReparseLoading: false, + wordRefBatchSaving: false, + wordRefBatchRows: [], + wordRefBatchUploadedCount: 0, + wordRefBatchEditingKey: '', + wordRefBatchExistingExpanded: {} }; }, computed: { zyModeEnabled() { return isZyModeEnabled(this.$route); }, + wordRefBatchDialogTitle() { + const base = this.$t('commonTable.wordRefBatchDialogTitle'); + const selected = (this.wordRefBatchRows || []).filter(function (row) { + return row && row.apply; + }).length; + return base + ' (' + selected + '/' + (this.wordRefBatchRows || []).length + ')'; + }, + wordRefBatchCountMismatch() { + return this.wordRefBatchUploadedCount > 0 && this.wordRefBatchUploadedCount !== (this.chanFerForm || []).length; + }, showRefRelevanceToolbar() { return this.role === 'editor' && this.showB_step === 2 && !this.refRelevanceStatusLoading; }, @@ -1302,6 +1643,14 @@ export default { }, resolveRelevanceDesc(item) { if (!item) return ''; + const authorComment = String( + item.author_comment != null + ? item.author_comment + : item.authorComment != null + ? item.authorComment + : '' + ).trim(); + if (authorComment) return authorComment; const reason = String(item.reason || '').trim(); if (reason) return reason; const isZh = this.$i18n && this.$i18n.locale === 'zh'; @@ -1318,6 +1667,18 @@ export default { if (v > 1 && v <= 100) return Math.round(v); return null; }, + resolveRelevanceScoreRaw(item) { + if (!item || typeof item !== 'object') return null; + if (item.relevance_score != null && item.relevance_score !== '') return item.relevance_score; + if (item.confidence != null && item.confidence !== '') return item.confidence; + if (item.relevanceScore != null && item.relevanceScore !== '') return item.relevanceScore; + return null; + }, + formatRelevanceItemScore(item, isFailed, isDone) { + if (isFailed) return 0; + if (!isDone) return null; + return this.formatRelevanceConfidenceScore(this.resolveRelevanceScoreRaw(item)); + }, isRefRelevanceRecordStatusFailed(item) { return Number(item && item.check_status) === REF_RELEVANCE_RECORD_STATUS_FAILED; }, @@ -1344,9 +1705,9 @@ export default { checkStatus === REF_RELEVANCE_RECORD_STATUS_PENDING || checkStatus === 1 || (!isDone && !isFailed && isNaN(checkStatus)); - const score = - isFailed ? 0 : isDone ? this.formatRelevanceConfidenceScore(item.confidence) : null; + const score = this.formatRelevanceItemScore(item, isFailed, isDone); const pass = isFailed ? false : isDone ? this.resolveProgressRecordPass(item) : false; + const confidenceRaw = this.resolveRelevanceScoreRaw(item); return { am_id: item.am_id != null ? item.am_id : item.wn_id, check_id: item.check_id, @@ -1361,9 +1722,11 @@ export default { cite_total: total, check_status: isNaN(checkStatus) ? null : checkStatus, score: score, - confidence: item.confidence, + confidence: confidenceRaw, + relevance_score: item.relevance_score, reason: String(item.reason || '').trim(), ai_desc: String(item.reason || '').trim(), + author_comment: String(item.author_comment || item.authorComment || '').trim(), is_match: item.is_match, is_pass: pass, need_modify: isPending ? false : isFailed ? true : !pass, @@ -1757,6 +2120,97 @@ export default { resolve(); } }, + async ensureArticleMainsForReport() { + let articleId = this.resolvedArticleId; + if (!articleId) { + articleId = await this.loadArticleIdFromProduction(); + } + if (!articleId) { + return (this.articleMainList || []).slice(); + } + try { + const res = await this.$api.post('api/Preaccept/getArticleMainsNew', { + article_id: articleId + }); + if (res.code == 0) { + const list = (res.data.list || []).map((e) => ({ + ...e, + text: e.content + })); + this.articleMainList = list; + this.articleMainsLoaded = true; + return list.slice(); + } + } catch (err) { + console.warn('getArticleMainsNew failed', err); + } + return (this.articleMainList || []).slice(); + }, + buildAnnotationReportWordList(mainList) { + return (mainList || []).map(function (item) { + return { + am_id: item.am_id != null ? item.am_id : item.p_main_id, + content: item.content != null ? item.content : item.text || '', + type: item.type, + image: item.image, + table: item.table, + is_h1: item.is_h1, + is_h2: item.is_h2, + is_h3: item.is_h3 + }; + }); + }, + async handleDownloadAnnotationReport() { + if (this.annotationReportLoading) return; + if (!this.p_article_id) { + this.$message.warning(this.$t('commonTable.refAnnotationReportNoArticle')); + return; + } + + this.annotationReportLoading = true; + try { + const progress = await fetchReferenceCheckProgress(this.$api, this.p_article_id); + const flatRecords = flattenProgressRecords(progress.list); + if (!flatRecords.length) { + this.$message.warning(this.$t('commonTable.refAnnotationReportNoData')); + return; + } + + const mainList = await this.ensureArticleMainsForReport(); + const wordList = this.buildAnnotationReportWordList(mainList); + if (!wordList.length) { + this.$message.warning(this.$t('commonTable.citeRelevanceNoContent')); + return; + } + + const articleId = this.resolvedArticleId || String(this.article_id || ''); + const refCount = Number(progress.total_groups) || (this.chanFerForm || []).length; + const labels = buildReferenceAnnotationReportLabels(this.$t.bind(this)); + const refSourceName = + 'references-editor' + (articleId ? '-' + articleId : '') + '.html'; + const fileName = + (articleId || 'manuscript') + + '_' + + (this.$i18n && this.$i18n.locale === 'zh' ? '批注分析报告' : 'annotation-report') + + '.html'; + + downloadReferenceAnnotationReportHtml({ + wordList: wordList, + progressList: progress.list, + labels: labels, + articleId: articleId, + refCount: refCount, + refSourceName: refSourceName, + fileName: fileName + }); + this.$message.success(this.$t('commonTable.refAnnotationReportSuccess')); + } catch (err) { + console.error(err); + this.$message.error(this.$t('commonTable.refAnnotationReportFail')); + } finally { + this.annotationReportLoading = false; + } + }, fetchRefRelevanceProgressCache() { if (this.role !== 'editor' || this.p_article_id == null || this.p_article_id === '') { return Promise.resolve(null); @@ -2226,7 +2680,7 @@ export default { if (item.is_pass === false || item.is_pass === 0 || item.is_pass === '0') return false; if (item.is_match === 1 || item.is_match === '1') return true; if (item.is_match === 0 || item.is_match === '0') return false; - const score = this.formatRelevanceConfidenceScore(item.confidence); + const score = this.formatRelevanceConfidenceScore(this.resolveRelevanceScoreRaw(item)); if (score != null && score >= 65) return true; return false; }, @@ -2236,9 +2690,9 @@ export default { const checkStatus = Number(raw.status); const isFailed = checkStatus === REF_RELEVANCE_RECORD_STATUS_FAILED; const isDone = checkStatus === REF_RELEVANCE_RECORD_STATUS_DONE; - const score = - isFailed ? 0 : isDone ? this.formatRelevanceConfidenceScore(raw.confidence) : null; + const score = this.formatRelevanceItemScore(raw, isFailed, isDone); const pass = isFailed ? false : isDone ? this.resolveProgressRecordPass(raw) : false; + const confidenceRaw = this.resolveRelevanceScoreRaw(raw); return { am_id: raw.am_id != null ? raw.am_id : raw.wn_id, check_id: raw.check_id, @@ -2253,9 +2707,11 @@ export default { cite_total: total, check_status: isNaN(checkStatus) ? null : checkStatus, score: score, - confidence: raw.confidence, + confidence: confidenceRaw, + relevance_score: raw.relevance_score, reason: reason, ai_desc: reason, + author_comment: String(raw.author_comment || raw.authorComment || '').trim(), is_match: raw.is_match, is_pass: pass, need_modify: !pass, @@ -3038,6 +3494,12 @@ export default { score = c > 0 && c <= 1 ? c * 100 : c; } } + if ((isNaN(score) || score === 0) && item.relevance_score != null) { + const r = Number(item.relevance_score); + if (!isNaN(r)) { + score = r > 0 && r <= 1 ? r * 100 : r; + } + } return score; }, getRelevanceScoreClass(item) { @@ -4858,6 +5320,203 @@ export default { }); }, // 刷新组件 + grabReferencesFromWord() { + return this.handleGrabReferencesFromWord(); + }, + triggerWordRefBatchUpload() { + if (!this.zyModeEnabled) return; + const input = this.$refs.wordRefBatchFileInput; + if (input) { + input.value = ''; + input.click(); + } + }, + async handleGrabReferencesFromWord() { + if (!this.zyModeEnabled) return; + if (this.wordRefBatchLoading) return; + const manuscriptPath = String(this.gridData || '').trim(); + if (!manuscriptPath) { + this.triggerWordRefBatchUpload(); + return; + } + this.wordRefBatchLoading = true; + try { + const parsed = await parseManuscriptWordReferences(this.$api, { + manuscriptPath: manuscriptPath, + gridData: manuscriptPath, + mediaUrl: this.mediaUrl, + baseUrl: this.baseUrl, + useGridDataOnly: true + }); + this.openWordRefBatchDialog(parsed); + } catch (err) { + console.error(err); + this.$message.error(this.$t('commonTable.wordRefBatchParseFail')); + } finally { + this.wordRefBatchLoading = false; + } + }, + async handleWordRefBatchFileChange(event) { + const input = event && event.target; + const file = input && input.files && input.files[0]; + if (!file) return; + this.wordRefBatchLoading = true; + try { + const parsed = await parseLocalWordReferencesFile(file); + this.openWordRefBatchDialog(parsed); + } catch (err) { + console.error(err); + this.$message.error(this.$t('commonTable.wordRefBatchParseFail')); + } finally { + this.wordRefBatchLoading = false; + if (input) input.value = ''; + } + }, + openWordRefBatchDialog(parsed) { + const result = parsed || {}; + if (!result.found || !result.rows || !result.rows.length) { + this.$message.warning(this.$t('commonTable.wordRefsPreviewEmpty')); + return; + } + this.wordRefBatchUploadedCount = result.count || result.rows.length; + this.wordRefBatchRows = buildWordRefBatchRows(this.chanFerForm, result.rows); + this.wordRefBatchEditingKey = ''; + this.wordRefBatchExistingExpanded = {}; + this.wordRefBatchDialogVisible = true; + }, + wordRefBatchEditKey(row, field) { + return String(row.index) + ':' + field; + }, + isWordRefBatchEditing(row, field) { + return this.wordRefBatchEditingKey === this.wordRefBatchEditKey(row, field); + }, + startWordRefBatchEdit(row, field) { + if (!row || !row.apply) return; + if (field === 'joura' && row.refer_type !== 'journal') return; + this.wordRefBatchEditingKey = this.wordRefBatchEditKey(row, field); + this.$nextTick(() => { + const ref = this.$refs['wrbInput' + row.index + field]; + const target = Array.isArray(ref) ? ref[0] : ref; + if (!target) return; + if (target.focus) { + target.focus(); + return; + } + const input = target.$el && target.$el.querySelector('input, textarea'); + if (input && input.focus) input.focus(); + }); + }, + stopWordRefBatchEdit() { + this.wordRefBatchEditingKey = ''; + }, + wordRefBatchSepAfterTitle(row) { + const title = String((row && row.title) || '').trim(); + if (!title) return ''; + return ' '; + }, + wordRefBatchSepAfterJoura(row) { + const joura = String((row && row.joura) || '').trim(); + if (!joura) return ''; + return /\.\s*$/.test(joura) ? ' ' : '. '; + }, + isWordRefBatchParseFailed(row) { + return isWordRefBatchRowParseIncomplete(row); + }, + wordRefBatchFieldDefs(row) { + const keys = getWordRefBatchFieldKeys(row && row.refer_type); + const labelMap = { + author: this.$t('commonTable.wordRefBatchAuthor'), + title: this.$t('commonTable.wordRefBatchTitle'), + joura: this.$t('commonTable.wordRefBatchJournal'), + dateno: this.$t('commonTable.wordRefBatchDateno'), + doilink: this.$t('commonTable.wordRefBatchDoi'), + isbn: 'ISBN', + content: 'Content' + }; + return keys.map(function (key) { + return { + key: key, + label: labelMap[key] || key + }; + }); + }, + wordRefBatchWordPreview(row) { + return stripWordRefBatchHtmlPreview(row && row.wordHtml); + }, + onWordRefBatchTypeChange(row, referType) { + if (!row) { + return; + } + if (String(referType || '').toLowerCase() === 'other') { + applyWordRefBatchOtherType(row); + } + }, + toggleWordRefBatchExisting(row) { + if (!row) return; + const key = row.index; + this.$set(this.wordRefBatchExistingExpanded, key, !this.wordRefBatchExistingExpanded[key]); + }, + toggleWordRefBatchSelectAll(checked) { + (this.wordRefBatchRows || []).forEach(function (row) { + if (row && !row.missingUploaded && (row.p_refer_id || row.missingExisting)) { + row.apply = !!checked; + } + }); + }, + async reparseWordRefBatchWithServer() { + if (this.wordRefBatchReparseLoading || !this.$api) return; + this.wordRefBatchReparseLoading = true; + try { + this.wordRefBatchRows = await enrichWordRefBatchRowsWithDealContent(this.$api, this.wordRefBatchRows); + this.$message.success(this.$t('commonTable.wordRefBatchReparseSuccess')); + } catch (err) { + console.error(err); + this.$message.error(this.$t('commonTable.wordRefBatchReparseFail')); + } finally { + this.wordRefBatchReparseLoading = false; + } + }, + async saveWordRefBatchSelected() { + const rows = (this.wordRefBatchRows || []).filter(function (row) { + return row && row.apply && !row.missingUploaded && (row.p_refer_id || row.missingExisting); + }); + if (!rows.length) { + this.$message.warning(this.$t('commonTable.wordRefBatchNothingSelected')); + return; + } + if (this.wordRefBatchSaving) return; + this.wordRefBatchSaving = true; + const loading = this.$loading({ + lock: true, + text: 'Saving...', + spinner: 'el-icon-loading', + background: 'rgba(0, 0, 0, 0.7)' + }); + try { + const result = await saveWordRefBatchRows(this.$api, { + rows: this.wordRefBatchRows, + pArticleId: this.p_article_id, + role: this.role, + articleId: this.$route.query.id + }); + loading.close(); + if (result.ok > 0) { + this.$message.success( + this.$t('commonTable.wordRefBatchSaveSuccess', { ok: result.ok, fail: result.fail }) + ); + this.wordRefBatchDialogVisible = false; + this.afterReferMutationSuccess({}); + } else { + this.$message.error(this.$t('commonTable.wordRefBatchSaveFail')); + } + } catch (err) { + loading.close(); + console.error(err); + this.$message.error(this.$t('commonTable.wordRefBatchSaveFail')); + } finally { + this.wordRefBatchSaving = false; + } + }, refrashComp() { this.$emit('refrashComp', true); }, @@ -7614,4 +8273,249 @@ export default { color: #f56c6c; } +.word-ref-batch-warn { + margin-bottom: 10px; + padding: 8px 12px; + background: #fdf6ec; + border: 1px solid #f5dab1; + border-radius: 6px; + color: #e6a23c; + font-size: 13px; +} + +.word-ref-batch-actions { + margin-bottom: 10px; + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 8px; +} + +.word-ref-batch-hint { + font-size: 12px; + color: #909399; + margin-right: 4px; +} + +.word-ref-batch-list { + max-height: calc(100vh - 190px); + overflow-y: auto; + border: 1px solid #ebeef5; + border-radius: 6px; + background: #fff; +} + +.word-ref-batch-item { + padding: 6px 12px 8px; + border-bottom: 1px solid #ebeef5; + border-left: 3px solid #67c23a; +} + +.word-ref-batch-item:last-child { + border-bottom: none; +} + +.word-ref-batch-item.is-disabled { + opacity: 0.55; + border-left-color: #dcdfe6; +} + +.word-ref-batch-item.is-extra { + border-left-color: #e6a23c; +} + +.word-ref-batch-item.is-missing { + border-left-color: #909399; +} + +.word-ref-batch-item.is-parse-failed { + background: #fef0f0; + border-left-color: #f56c6c; +} + +.word-ref-batch-failed-tag { + font-size: 12px; + color: #f56c6c; + font-weight: 600; +} + +.word-ref-batch-fields-form { + margin-top: 6px; + padding: 8px 8px 4px 30px; + display: flex; + flex-direction: column; + gap: 8px; +} + +.word-ref-batch-word-original { + padding: 6px 8px; + background: rgba(255, 255, 255, 0.72); + border: 1px dashed #fbc4c4; + border-radius: 4px; + font-size: 12px; + line-height: 1.5; + color: #606266; +} + +.word-ref-batch-word-original-label { + color: #909399; + margin-right: 6px; + font-weight: 600; +} + +.word-ref-batch-word-original-text { + word-break: break-word; +} + +.word-ref-batch-field-row { + display: grid; + grid-template-columns: 120px minmax(0, 1fr); + gap: 8px; + align-items: start; +} + +.word-ref-batch-field-label { + font-size: 12px; + line-height: 28px; + color: #606266; + font-weight: 600; + text-align: right; + padding-right: 4px; +} + +.word-ref-batch-field-input, +.word-ref-batch-field-textarea { + width: 100%; +} + +.word-ref-batch-field-row .word-ref-batch-field-textarea ::v-deep textarea { + font-size: 13px; + line-height: 1.45; +} + +.word-ref-batch-fields-form--other { + padding-top: 4px; +} + +.word-ref-batch-field-row--full { + grid-template-columns: 88px minmax(0, 1fr); +} + +.word-ref-batch-field-row--full .word-ref-batch-field-label { + line-height: 1.4; + padding-top: 6px; +} + +.word-ref-batch-item-head { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 2px; +} + +.word-ref-batch-num { + font-weight: 700; + color: #303133; + font-size: 13px; + min-width: 22px; +} + +.word-ref-batch-type { + width: 92px; +} + +.word-ref-batch-compare-btn { + margin-left: auto; + padding: 0 4px; + font-size: 12px; +} + +.word-ref-batch-citation { + font-size: 13px; + line-height: 1.55; + color: #303133; + padding-left: 30px; + word-break: break-word; +} + +.word-ref-seg { + display: inline; +} + +.word-ref-seg.is-editable .word-ref-seg-text { + cursor: text; + border-bottom: 1px dashed transparent; + transition: border-color 0.15s, background 0.15s; +} + +.word-ref-seg.is-editable .word-ref-seg-text:hover { + border-bottom-color: #409eff; + background: #f0f7ff; +} + +.word-ref-seg-text.is-empty { + color: #c0c4cc; + font-style: italic; +} + +.word-ref-seg--italic .word-ref-seg-text { + font-style: italic; +} + +.word-ref-seg--link .word-ref-seg-text { + color: #0082aa; +} + +.word-ref-seg-input { + display: inline-block; + width: min(420px, 70vw); + vertical-align: baseline; +} + +.word-ref-seg-textarea { + display: block; + width: 100%; + margin-top: 4px; +} + +.word-ref-batch-existing { + margin-top: 4px; + padding: 4px 8px 4px 30px; + font-size: 12px; + line-height: 1.45; + color: #909399; + background: #fafafa; + border-radius: 4px; +} + +.word-ref-batch-existing-label { + color: #c0c4cc; + margin-right: 4px; +} + +.word-ref-batch-existing-text { + word-break: break-word; +} + +::v-deep .word-ref-batch-dialog .el-dialog__body { + padding: 10px 16px 12px; +} + +::v-deep .word-ref-batch-dialog .el-dialog { + margin-bottom: 0; +} + +::v-deep .word-ref-batch-citation .word-ref-seg-input .el-input__inner { + height: 26px; + line-height: 26px; + padding: 0 6px; + font-size: 13px; +} + +::v-deep .word-ref-batch-item-head .el-input__inner { + height: 24px; + line-height: 24px; + font-size: 12px; +} + \ No newline at end of file diff --git a/src/components/page/journalFeeApproval.vue b/src/components/page/journalFeeApproval.vue new file mode 100644 index 0000000..989144f --- /dev/null +++ b/src/components/page/journalFeeApproval.vue @@ -0,0 +1,897 @@ + + + + + diff --git a/src/components/page/per_history.vue b/src/components/page/per_history.vue index e6ad545..d81ed95 100644 --- a/src/components/page/per_history.vue +++ b/src/components/page/per_history.vue @@ -232,12 +232,38 @@ - - + +
    + + {{ $t('reviewHistory.exportCertificateImage') }} + + + {{ $t('reviewHistory.generateCertificatePdf') }} + +
    +
    -

    No certificate

    +

    {{ $t('reviewHistory.noCertificate') }}

    OK @@ -249,6 +275,8 @@ ' + + '
    ' + + leftHead + + '
    ' + + leftHtml + + '
    ' + + '
    ' + + rightHead + + '
    ' + + rightBody + + '
    ' + ); +} + +export function downloadReferenceAnnotationReportHtml(options) { + const html = buildReferenceAnnotationReportHtml(options); + const blob = new Blob([html], { type: 'text/html;charset=utf-8' }); + const url = URL.createObjectURL(blob); + const link = document.createElement('a'); + const articleId = (options && options.articleId) || ''; + link.href = url; + link.download = + (options && options.fileName) || + (articleId ? articleId + '_批注分析报告.html' : 'reference-annotation-report.html'); + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +} + +export async function fetchReferenceCheckProgress(api, pArticleId) { + if (!api || pArticleId == null || pArticleId === '') { + return { list: [], total_groups: 0 }; + } + const res = await api.post('api/References/referenceCheckProgressAI', { + p_article_id: pArticleId + }); + if (!res || res.code !== 0) { + const err = new Error((res && res.msg) || 'PROGRESS_FAILED'); + err.code = res && res.code; + throw err; + } + const data = res.data || {}; + return { + list: Array.isArray(data.list) ? data.list : [], + total_groups: Number(data.total_groups) || 0, + summary: data.summary || {} + }; +} + +export function buildReferenceAnnotationReportLabels(translate) { + const t = translate || function (key) { + return key; + }; + return { + pageTitleSuffix: t('commonTable.refAnnotationReportTitleSuffix'), + manuscriptPreview: t('commonTable.refAnnotationReportManuscriptPreview'), + refMatchPrefix: t('commonTable.refAnnotationReportRefMatchPrefix'), + refLibraryPrefix: t('commonTable.refAnnotationReportRefLibraryPrefix'), + refTotal: t('commonTable.refAnnotationReportRefTotal'), + analysisTitle: t('commonTable.refAnnotationReportAnalysisTitle'), + statTotal: t('commonTable.refAnnotationReportStatTotal'), + statDirect: t('commonTable.refAnnotationReportStatDirect'), + statStrong: t('commonTable.refAnnotationReportStatStrong'), + statWeak: t('commonTable.refAnnotationReportStatWeak'), + statNone: t('commonTable.refAnnotationReportStatNone'), + locateTip: t('commonTable.refAnnotationReportLocateTip'), + locatePara: t('commonTable.refAnnotationReportLocatePara'), + paraRefsPrefix: t('commonTable.refAnnotationReportParaRefsPrefix'), + verdictLabel: t('commonTable.refAnnotationReportVerdictLabel'), + reasonLabel: t('commonTable.refAnnotationReportReasonLabel'), + authorCommentLabel: t('commonTable.refAnnotationReportAuthorCommentLabel'), + noReason: t('commonTable.refAnnotationReportNoReason'), + conclusionLabel: t('commonTable.refAnnotationReportConclusionLabel'), + conclusionKeep: t('commonTable.refAnnotationReportConclusionKeep'), + conclusionRevise: t('commonTable.refAnnotationReportConclusionRevise'), + noConclusion: t('commonTable.refAnnotationReportNoConclusion'), + scoreLabel: t('commonTable.refAnnotationReportScoreLabel'), + citeLocLabel: t('commonTable.refAnnotationReportCiteLocLabel'), + citeParaSuffix: t('commonTable.refAnnotationReportCiteParaSuffix'), + paraSectionTitle: t('commonTable.refAnnotationReportParaSectionTitle'), + refConclusionSectionTitle: t('commonTable.refAnnotationReportRefConclusionSectionTitle'), + refCiteTotal: t('commonTable.refAnnotationReportRefCiteTotal'), + summaryTitle: t('commonTable.refAnnotationReportSummaryTitle'), + summaryPrefix: t('commonTable.refAnnotationReportSummaryPrefix'), + summaryRefPrefix: t('commonTable.refAnnotationReportSummaryRefPrefix'), + warnBadge: t('commonTable.refAnnotationReportWarnBadge'), + issueNavTitle: t('commonTable.refAnnotationReportIssueNavTitle'), + issueNavHint: t('commonTable.refAnnotationReportIssueNavHint'), + issueCountLabel: t('commonTable.refAnnotationReportIssueCountLabel'), + jumpToReview: t('commonTable.refAnnotationReportJumpToReview'), + jumpToReviewTip: t('commonTable.refAnnotationReportJumpToReviewTip'), + locateSource: t('commonTable.refAnnotationReportLocateSource'), + paraBadge: t('commonTable.refAnnotationReportParaBadge'), + paraOkLabel: t('commonTable.refAnnotationReportParaOkLabel'), + legendIssue: t('commonTable.refAnnotationReportLegendIssue'), + legendOk: t('commonTable.refAnnotationReportLegendOk'), + legendCite: t('commonTable.refAnnotationReportLegendCite'), + noData: t('commonTable.refAnnotationReportNoData'), + defaultTitle: t('commonTable.refAnnotationReportDefaultTitle'), + levelDirect: t('commonTable.refAnnotationReportLevelDirect'), + levelStrong: t('commonTable.refAnnotationReportLevelStrong'), + levelWeak: t('commonTable.refAnnotationReportLevelWeak'), + levelNone: t('commonTable.refAnnotationReportLevelNone') + }; +} diff --git a/src/utils/manuscriptReferenceHtml.js b/src/utils/manuscriptReferenceHtml.js index cc27fa5..30882f7 100644 --- a/src/utils/manuscriptReferenceHtml.js +++ b/src/utils/manuscriptReferenceHtml.js @@ -131,6 +131,7 @@ export function buildReferencesEditableHtml(references, labels) { const intro = L.intro || ''; const saveTip = L.saveTip || ''; const downloadBtn = L.downloadEdited || 'Download edited HTML'; + const copyBracketBtn = L.copyBracket || 'Copy [n] format'; const refTitle = L.referencesTitle || 'References'; const bulkHint = L.bulkHint || ''; const bulkContent = buildReferencesBulkHtml(list); @@ -147,6 +148,7 @@ 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--secondary{border-color:#67c23a;background:#67c23a}' + '.ref-panel{background:#fff;border-radius:8px;padding:20px 24px;box-shadow:0 1px 4px rgba(0,0,0,.08);font-size:12px}' + '.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)}' + @@ -157,6 +159,7 @@ export function buildReferencesEditableHtml(references, labels) { '
    ' + escapeHtml(intro) + '
    ' + '
    ' + '' + + '' + '
    ' + '
    ' + escapeHtml(saveTip) + '
    ' + '' + @@ -165,16 +168,9 @@ export function buildReferencesEditableHtml(references, labels) { '
    ' + bulkContent + '
    ' + - '