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/components/common/langs/en.js b/src/components/common/langs/en.js index 5d8f87a..17df994 100644 --- a/src/components/common/langs/en.js +++ b/src/components/common/langs/en.js @@ -50,6 +50,8 @@ const en = { plagiarismNotChecked: 'Not checked', plagiarismChecking: 'Checking…', plagiarismRecheck: 'Re-check', + plagiarismRecheckConfirm: 'Start a new plagiarism check for this manuscript?', + plagiarismRecheckCancel: 'Cancel', plagiarismDuplicateCheck: 'Re-check', plagiarismCheckFailed: 'Failed to start plagiarism check.', plagiarismStatusFailed: 'Failed to load plagiarism status.', @@ -62,6 +64,9 @@ const en = { plagiarismSimilarity: 'Similarity', plagiarismFile: 'File', plagiarismPreviewPdf: 'Preview report', + plagiarismDownloadReport: 'Download report', + plagiarismOnlinePreview: 'Online preview', + plagiarismOnlinePreviewFailed: 'Failed to load online preview URL.', plagiarismReportLink: 'Report', plagiarismNoPdfLink: 'No link', plagiarismPreviewClose: 'Close', @@ -1569,8 +1574,48 @@ const en = { cancel: 'Cancel' }, refRelevance: { - startDetect: 'Start relevance check', - detecting: 'Detecting…', + startDetect: 'Batch Audit Reference Context', + startDetectTip: + 'Launch AI semantic consistency verification for all references against in-text citations.', + checkComplete: 'Relevance check completed', + detectingAi: 'Proofreading', + auditProgressTitle: 'Batch reference context audit in progress', + queuePositionLabel: 'Queue position:', + queuePositionNum: 'No. {n}', + queueAheadPrefix: 'Ahead:', + queueAheadSuffix: ' article(s) waiting for reference relevance check', + progressCount: '{done}/{total}', + progressCountShort: '{done} done', + startFailed: 'Failed to start proofreading', + statusFailed: 'Failed to fetch proofreading status', + progressFailed: 'Failed to fetch check progress', + detailsFailed: 'Failed to load AI proofreading details', + detailsEmpty: 'No AI proofreading details available', + noReferId: 'Missing reference ID', + noArticleId: 'Missing article ID; cannot run check', + detecting: 'Checking', + pendingDetect: 'Pending', + detectFailed: 'Check failed', + notFound: 'Not found', + copyInsight: 'Copy analysis', + copySuccess: 'Copied', + copyFailed: 'Copy failed', + copyEmpty: 'Nothing to copy', + redetect: 'Re-check', + redetectFailed: 'Re-check failed', + noReason: '(No details)', + aiAnalysis: 'AI analysis', + expandReason: 'Expand', + collapseReason: 'Collapse', + expandChips: 'Show all AI Relevance Assessment Results', + collapseChips: 'Hide AI Relevance Assessment Results', + expandChipsAll: 'Expand all ({n})', + collapseChipsAll: 'Collapse all', + summaryPartial: 'Checking {pending}, {done} done', + summaryPartialDone: ', {done} done', + summaryPartialPending: 'Checking {pending}', + summaryChecking: 'Checking', + pendingCites: '{n} more checking…', filterAll: 'All ({count})', filterModify: 'Needs revision ({count})', columnTitle: 'AI citation relevance review', @@ -1578,6 +1623,7 @@ const en = { uncitedDesc: 'This reference appears in the list but no matching in-text citation was found.', uncitedTip: 'Remove it from the reference list or add an in-text citation in the manuscript.', citationN: 'Cite {n}', + citeInParagraphN: 'Occurrence {n} in paragraph', relevancePct: 'Relevance {score}%', relevancePctShort: '{score}%', viewAiAnalysis: 'View AI analysis →', @@ -1591,7 +1637,7 @@ const en = { locationMatcher: 'Location context matcher', manuscriptSection: 'Manuscript section', highlightedMatch: 'Highlighted match', - excerptEmpty: 'No excerpt available yet.', + excerptEmpty: 'Original cited content not found', annotationComment: 'Comment', annotationDelete: 'Suggest removal', annotationRevision: 'Revision', diff --git a/src/components/common/langs/zh.js b/src/components/common/langs/zh.js index d558ff1..b5c1363 100644 --- a/src/components/common/langs/zh.js +++ b/src/components/common/langs/zh.js @@ -48,6 +48,8 @@ const zh = { plagiarismNotChecked: '未检测', plagiarismChecking: '正在检测…', plagiarismRecheck: '重新查重', + plagiarismRecheckConfirm: '确认要重新发起查重吗?', + plagiarismRecheckCancel: '取消', plagiarismDuplicateCheck: '重复检查', plagiarismCheckFailed: '查重任务启动失败。', plagiarismStatusFailed: '获取查重状态失败。', @@ -60,6 +62,9 @@ const zh = { plagiarismSimilarity: '相似度', plagiarismFile: '文件', plagiarismPreviewPdf: '预览报告', + plagiarismDownloadReport: '下载报告', + plagiarismOnlinePreview: '在线预览', + plagiarismOnlinePreviewFailed: '获取在线预览地址失败。', plagiarismReportLink: '报告', plagiarismNoPdfLink: '无链接', plagiarismPreviewClose: '关闭', @@ -1550,8 +1555,47 @@ const zh = { cancel: '取消' }, refRelevance: { - startDetect: '开始检测相关性', + startDetect: '全量引文相关性核查', + startDetectTip: '一键启动对本篇稿件全部参考文献与正文语境的 AI 一致性核查', + checkComplete: '相关性检测已完成', + detectingAi: '校对中', + auditProgressTitle: '全量引文相关性核查中', + queuePositionLabel: '当前排队位置:', + queuePositionNum: '第 {n} 篇', + queueAheadPrefix: '前方有', + queueAheadSuffix: ' 篇文章参考文献相关性待检测', + progressCount: '{done}/{total}', + progressCountShort: '已完成 {done}', + startFailed: '启动校对失败', + statusFailed: '获取校对状态失败', + progressFailed: '获取检测进度失败', + detailsFailed: '获取 AI 校对详情失败', + detailsEmpty: '暂无 AI 校对详情', + noReferId: '缺少参考文献 ID', + noArticleId: '缺少稿件 ID,无法检测', detecting: '检测中…', + pendingDetect: '待检测', + detectFailed: '检测失败', + notFound: '未找到', + copyInsight: '复制分析', + copySuccess: '已复制', + copyFailed: '复制失败', + copyEmpty: '暂无可复制内容', + redetect: '重新检测', + redetectFailed: '重新检测失败', + noReason: '(暂无说明)', + aiAnalysis: 'AI 分析', + expandReason: '展开', + collapseReason: '收起', + expandChips: '显示全部 AI 相关性评定结果', + collapseChips: '隐藏 AI 相关性评定结果', + expandChipsAll: '展开全部({n})', + collapseChipsAll: '收起全部', + summaryPartial: '正在检测 {pending} 处,已完成 {done} 处', + summaryPartialDone: ',已完成 {done} 处', + summaryPartialPending: '正在检测 {pending} 处', + summaryChecking: '正在检测', + pendingCites: '另有 {n} 处检测中…', filterAll: '全部({count})', filterModify: '需修改({count})', columnTitle: 'AI 智能评定相关性建议', @@ -1559,6 +1603,7 @@ const zh = { uncitedDesc: '该文献已列入参考文献列表,但正文中未找到对应的引用上标。', uncitedTip: '建议:从参考文献列表中删除,或在正文中补充引用。', citationN: '第{n}处', + citeInParagraphN: '本段第{n}处', relevancePct: '相关性 {score}%', relevancePctShort: '{score}%', viewAiAnalysis: '查看 AI 分析 →', @@ -1572,7 +1617,7 @@ const zh = { locationMatcher: '引用位置匹配', manuscriptSection: '稿件段落', highlightedMatch: '高亮匹配', - excerptEmpty: '暂无段落原文,待接口返回', + excerptEmpty: '原引用内容未找到', annotationComment: '批注', annotationDelete: '建议删除', annotationRevision: '修订', diff --git a/src/components/page/PreIngestedEditor.vue b/src/components/page/PreIngestedEditor.vue index 9c07386..ae8ed63 100644 --- a/src/components/page/PreIngestedEditor.vue +++ b/src/components/page/PreIngestedEditor.vue @@ -1343,7 +1343,7 @@ export default { p_article_id: this.p_article_id }) .then((res) => { - this.chanFerForm = res.data.refers; + this.chanFerForm = (res.data.refers || []).slice(); this.chanFerFormRepeatList = Object.values(res.data.repeat); for (var i = 0; i < this.chanFerForm.length; i++) { this.chanFerForm[i].edit_mark = 1; diff --git a/src/components/page/PreIngestedEditorProduce.vue b/src/components/page/PreIngestedEditorProduce.vue index 73c5579..7b556ee 100644 --- a/src/components/page/PreIngestedEditorProduce.vue +++ b/src/components/page/PreIngestedEditorProduce.vue @@ -1439,7 +1439,7 @@ export default { p_article_id: this.p_article_id }) .then((res) => { - this.chanFerForm = res.data.refers; + this.chanFerForm = (res.data.refers || []).slice(); this.chanFerFormRepeatList = Object.values(res.data.repeat); for (var i = 0; i < this.chanFerForm.length; i++) { this.chanFerForm[i].edit_mark = 1; diff --git a/src/components/page/articleDetailEditor.vue b/src/components/page/articleDetailEditor.vue index 2323633..37e5ae9 100644 --- a/src/components/page/articleDetailEditor.vue +++ b/src/components/page/articleDetailEditor.vue @@ -976,27 +976,29 @@ element-loading-background="transparent" >
- {{ $t('articleListEditor.plagiarismListTitle') }} -
- +
+ {{ $t('articleListEditor.plagiarismListTitle') }} {{ $t('articleListEditor.plagiarismRefresh') }}
+ + {{ $t('articleListEditor.plagiarismRecheck') }} +
- {{ $t('articleListEditor.plagiarismPreviewPdf') }} - + + + {{ $t('articleListEditor.plagiarismOnlinePreview') }} {{ $t('articleListEditor.plagiarismNoPdfLink') }} @@ -1870,7 +1875,8 @@ export default { plagiarismPdfPreviewVisible: false, plagiarismPdfPreviewUrl: '', plagiarismPdfPreviewTitle: '', - plagiarismPdfPreviewLoading: false + plagiarismPdfPreviewLoading: false, + plagiarismOnlinePreviewCheckId: null }; }, async created() { @@ -3051,6 +3057,22 @@ export default { this.plagiarismPollTimer = null; } }, + async confirmPlagiarismRecheck() { + try { + await this.$confirm( + this.$t('articleListEditor.plagiarismRecheckConfirm'), + this.$t('articleListEditor.plagiarismRecheck'), + { + confirmButtonText: this.$t('articleListEditor.plagiarismRecheck'), + cancelButtonText: this.$t('articleListEditor.plagiarismRecheckCancel'), + type: 'warning' + } + ); + await this.submitPlagiarismCheck(); + } catch (e) { + /* 用户取消 */ + } + }, async submitPlagiarismCheck() { const articleId = String((this.editform && this.editform.articleId) || this.$route.query.id || '').trim(); if (!articleId) { @@ -3149,12 +3171,31 @@ export default { if (n < 30) return 'sim-low'; return 'sim-high'; }, + formatPlagiarismDateTime(tsSeconds) { + const date = new Date(tsSeconds * 1000); + if (isNaN(date.getTime())) return '—'; + const pad = (v) => String(v).padStart(2, '0'); + return ( + date.getFullYear() + + '-' + + pad(date.getMonth() + 1) + + '-' + + pad(date.getDate()) + + ' ' + + pad(date.getHours()) + + ':' + + pad(date.getMinutes()) + + ':' + + pad(date.getSeconds()) + ); + }, formatPlagiarismDate(row) { if (!row || typeof row !== 'object') return '—'; const raw = row.finish_time || row.finished_at || row.update_time || + row.utime || row.ctime || row.create_time || row.created_at || @@ -3163,27 +3204,26 @@ export default { const s = String(raw).trim(); if (/^\d+$/.test(s)) { const num = Number(s); - const ts = num > 1e12 ? num : num * 1000; - try { - return this.formatDate(Math.floor(ts / 1000)); - } catch (e) { - return s; - } + const tsSec = num > 1e12 ? Math.floor(num / 1000) : num; + return this.formatPlagiarismDateTime(tsSec); } const d = new Date(s.replace(/-/g, '/')); if (!isNaN(d.getTime())) { - const pad = (v) => String(v).padStart(2, '0'); - return d.getFullYear() + '-' + pad(d.getMonth() + 1) + '-' + pad(d.getDate()); + return this.formatPlagiarismDateTime(Math.floor(d.getTime() / 1000)); } - return s.length > 10 ? s.slice(0, 10) : s; + return s.length > 19 ? s.slice(0, 19) : s; }, resolvePlagiarismPdfUrl(row) { if (!row || typeof row !== 'object') return ''; const raw = String(row.viewer_url || row.local_pdf_url || row.localPdfUrl || '').trim(); - if (!raw) return ''; - if (/^https?:\/\//i.test(raw)) return raw; + return this.normalizePlagiarismViewUrl(raw); + }, + normalizePlagiarismViewUrl(raw) { + const url = String(raw || '').trim(); + if (!url) return ''; + if (/^https?:\/\//i.test(url)) return url; - let path = raw.replace(/^\/+/, ''); + let path = url.replace(/^\/+/, ''); if (!/^public\//i.test(path)) { const media = String(this.mediaUrl || '/public/').replace(/\/+$/, ''); if (/^https?:\/\//i.test(media)) { @@ -3200,14 +3240,66 @@ export default { } return path; }, + getPlagiarismCheckId(row) { + if (!row || typeof row !== 'object') return null; + const id = row.check_id != null ? row.check_id : row.id; + return id != null && id !== '' ? id : null; + }, + canPlagiarismOnlinePreview(row) { + if (!row || this.isPlagiarismStateLoading(row)) return false; + if (!this.getPlagiarismCheckId(row)) return false; + const s = Number(row.state); + if (s === 1 || s === 4 || s === 5) return false; + return true; + }, + isPlagiarismOnlinePreviewLoading(row) { + const id = this.getPlagiarismCheckId(row); + return id != null && String(this.plagiarismOnlinePreviewCheckId) === String(id); + }, + async fetchPlagiarismReportUrl(checkId) { + const res = await this.$api.post('api/Plagiarism/getReportUrl', { check_id: checkId }); + if (!res || res.code !== 0) { + throw new Error((res && res.msg) || this.$t('articleListEditor.plagiarismOnlinePreviewFailed')); + } + const data = res.data; + let url = ''; + if (typeof data === 'string') { + url = data.trim(); + } else if (data && typeof data === 'object') { + url = String( + data.view_only_url || + data.url || + data.report_url || + data.viewer_url || + data.online_url || + '' + ).trim(); + } + if (!url) { + throw new Error(this.$t('articleListEditor.plagiarismNoReportUrl')); + } + return this.normalizePlagiarismViewUrl(url); + }, + async openPlagiarismOnlinePreview(row) { + const checkId = this.getPlagiarismCheckId(row); + if (!checkId) return; + this.plagiarismOnlinePreviewCheckId = checkId; + try { + const url = await this.fetchPlagiarismReportUrl(checkId); + window.open(url, '_blank', 'noopener,noreferrer'); + } catch (err) { + this.$message.error( + (err && err.message) || this.$t('articleListEditor.plagiarismOnlinePreviewFailed') + ); + } finally { + this.plagiarismOnlinePreviewCheckId = null; + } + }, hasPlagiarismPdf(row) { return !!this.resolvePlagiarismPdfUrl(row); }, openPlagiarismReportPage(row) { - - const url = this.resolvePlagiarismPdfUrl(row); - if (!url) return; window.open(url, '_blank', 'noopener,noreferrer'); }, @@ -3371,13 +3463,42 @@ export default { justify-content: space-between; margin-bottom: 8px; } -.plagiarism-check-actions { - display: flex; +.plagiarism-check-header-left { + display: inline-flex; align-items: center; - gap: 4px; + gap: 12px; + min-width: 0; + flex: 1; } -.plagiarism-check-actions .el-button { - padding: 0 6px; +.plagiarism-refresh-btn.el-button--text { + padding: 0 4px; + font-size: 12px; + color: #909399; +} +.plagiarism-refresh-btn.el-button--text:hover, +.plagiarism-refresh-btn.el-button--text:focus { + color: #409eff; +} +.plagiarism-recheck-btn.el-button--mini { + padding: 8px 14px; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.2px; + border: none; + border-radius: 4px; + color: #fff; + background: linear-gradient(135deg, #2ec4b6 0%, #0d9b8f 45%, #0a7f76 100%); + box-shadow: 0 2px 6px rgba(13, 155, 143, 0.35); + flex-shrink: 0; +} +.plagiarism-recheck-btn.el-button--primary:hover, +.plagiarism-recheck-btn.el-button--primary:focus { + color: #fff; + background: linear-gradient(135deg, #3dd4c6 0%, #14b0a3 45%, #0e948a 100%); + box-shadow: 0 3px 10px rgba(13, 155, 143, 0.45); +} +.plagiarism-recheck-btn.el-button--primary.is-loading { + background: linear-gradient(135deg, #2ec4b6 0%, #0d9b8f 45%, #0a7f76 100%); } .plagiarism-check-title { font-size: 13px; @@ -3437,7 +3558,9 @@ export default { } .plagiarism-sim-date { flex-shrink: 0; + min-width: 132px; color: #909399; + font-variant-numeric: tabular-nums; } .plagiarism-sim-state { flex: 1; @@ -3473,20 +3596,22 @@ export default { display: inline-flex; align-items: center; justify-content: flex-end; - gap: 6px; + gap: 16px; + margin-left: auto; } -.plagiarism-report-preview { - color: #409eff; +.plagiarism-report-online { + color: #006699; text-decoration: none; display: inline-flex; align-items: center; gap: 4px; + font-size: 12px; } -.plagiarism-report-preview:hover { +.plagiarism-report-online:hover { text-decoration: underline; - color: #66b1ff; + color: #0088bb; } -.plagiarism-report-preview .el-icon-link { +.plagiarism-report-online .el-icon-view { font-size: 14px; } .plagiarism-check-no-pdf { diff --git a/src/components/page/articleDetailEmail.vue b/src/components/page/articleDetailEmail.vue index 850d0a7..c5ce429 100644 --- a/src/components/page/articleDetailEmail.vue +++ b/src/components/page/articleDetailEmail.vue @@ -15,13 +15,6 @@ -

@@ -69,13 +62,11 @@