This commit is contained in:
2026-05-19 14:17:28 +08:00
parent 2a7b9a0ec2
commit 7570e0a1eb
4 changed files with 494 additions and 9 deletions

View File

@@ -50,10 +50,22 @@ const en = {
plagiarismNotChecked: 'Not checked',
plagiarismChecking: 'Checking…',
plagiarismRecheck: 'Re-check',
plagiarismDuplicateCheck: 'Re-check',
plagiarismCheckFailed: 'Failed to start plagiarism check.',
plagiarismStatusFailed: 'Failed to load plagiarism status.',
plagiarismNoReportUrl: 'Report link is not available yet.',
plagiarismReportDetailFailed: 'Could not load manuscript details. Please try again.',
plagiarismListTitle: 'Plagiarism check history',
plagiarismAutoCheck: 'Auto plagiarism check',
plagiarismRefresh: 'Refresh',
plagiarismEmptyList: 'No plagiarism checks yet',
plagiarismSimilarity: 'Similarity',
plagiarismFile: 'File',
plagiarismPreviewPdf: 'Preview report',
plagiarismReportLink: 'Report',
plagiarismNoPdfLink: 'No link',
plagiarismPreviewClose: 'Close',
plagiarismPreviewOpenTab: 'Open in new tab',
},
menu: {
main: 'Personal Center',

View File

@@ -48,10 +48,22 @@ const zh = {
plagiarismNotChecked: '未检测',
plagiarismChecking: '正在检测…',
plagiarismRecheck: '重新查重',
plagiarismDuplicateCheck: '重复检查',
plagiarismCheckFailed: '查重任务启动失败。',
plagiarismStatusFailed: '获取查重状态失败。',
plagiarismNoReportUrl: '报告链接暂不可用。',
plagiarismReportDetailFailed: '获取稿件详情失败,请稍后重试。',
plagiarismListTitle: '自动查重记录',
plagiarismAutoCheck: '自动查重',
plagiarismRefresh: '刷新',
plagiarismEmptyList: '暂无查重记录',
plagiarismSimilarity: '相似度',
plagiarismFile: '文件',
plagiarismPreviewPdf: '预览报告',
plagiarismReportLink: '报告',
plagiarismNoPdfLink: '无链接',
plagiarismPreviewClose: '关闭',
plagiarismPreviewOpenTab: '新窗口打开',
},
menu: {
main: '个人中心',

View File

@@ -947,6 +947,77 @@
</a>
<el-button type="text" @click="changeRepe" icon="el-icon-edit">Change</el-button>
</div>
<div v-if="plagiarismListReady" class="plagiarism-check-shell">
<el-button
v-if="!plagiarismList.length"
class="plagiarism-auto-check-btn"
icon="el-icon-search"
:loading="plagiarismSubmitLoading"
@click="submitPlagiarismCheck"
>
{{ $t('articleListEditor.plagiarismAutoCheck') }}
</el-button>
<div
v-else
class="plagiarism-check-block"
v-loading="plagiarismListLoading"
element-loading-background="transparent"
>
<div class="plagiarism-check-header">
<span class="plagiarism-check-title">{{ $t('articleListEditor.plagiarismListTitle') }}</span>
<div class="plagiarism-check-actions">
<!-- <el-button
type="text"
size="mini"
icon="el-icon-document-checked"
:loading="plagiarismSubmitLoading"
@click="submitPlagiarismCheck"
>
{{ $t('articleListEditor.plagiarismDuplicateCheck') }}
</el-button> -->
<el-button
type="text"
size="mini"
icon="el-icon-refresh"
:loading="plagiarismListLoading"
@click="fetchPlagiarismList(true)"
>
{{ $t('articleListEditor.plagiarismRefresh') }}
</el-button>
</div>
</div>
<div class="plagiarism-check-list-wrap">
<div
v-for="row in plagiarismList"
:key="row.check_id || row.id"
class="plagiarism-check-row"
:title="row.source_file_name || ''"
>
<span class="plagiarism-sim-dot" :class="getPlagiarismSimilarityLevel(row)"></span>
<span class="plagiarism-sim-pct" :class="getPlagiarismSimilarityLevel(row)">
{{ formatPlagiarismSimilarity(row) }}
</span>
<span class="plagiarism-sim-date">{{ formatPlagiarismDate(row) }}</span>
<span class="plagiarism-sim-state" :class="getPlagiarismStateClass(row)">
{{ formatPlagiarismStateLabel(row) }}
</span>
<span class="plagiarism-sim-report">
<a
v-if="hasPlagiarismPdf(row)"
href="javascript:;"
class="plagiarism-report-preview"
:title="$t('articleListEditor.plagiarismPreviewOpenTab')"
@click.prevent="openPlagiarismReportPage(row)"
>
{{ $t('articleListEditor.plagiarismPreviewPdf') }}
<i class="el-icon-link"></i>
</a>
<span v-else class="plagiarism-check-no-pdf">{{ $t('articleListEditor.plagiarismNoPdfLink') }}</span>
</span>
</div>
</div>
</div>
</div>
<div>
<span style="display: inline-block; vertical-align: top; margin-top: 7px">Manuscript : </span>
<el-upload
@@ -1142,6 +1213,31 @@
<el-button type="primary" @click="saveRepe">Save</el-button>
</span>
</el-dialog>
<el-dialog
:title="plagiarismPdfPreviewTitle"
:visible.sync="plagiarismPdfPreviewVisible"
width="90%"
top="4vh"
append-to-body
custom-class="plagiarism-pdf-preview-dialog"
@closed="onPlagiarismPdfPreviewClosed"
>
<div v-loading="plagiarismPdfPreviewLoading" class="plagiarism-pdf-preview-body">
<iframe
v-if="plagiarismPdfPreviewUrl"
:src="plagiarismPdfPreviewUrl"
class="plagiarism-pdf-preview-iframe"
frameborder="0"
@load="plagiarismPdfPreviewLoading = false"
></iframe>
</div>
<span slot="footer" class="dialog-footer">
<el-button size="small" @click="plagiarismPdfPreviewVisible = false">{{ $t('articleListEditor.plagiarismPreviewClose') }}</el-button>
<el-button size="small" type="primary" v-if="plagiarismPdfPreviewUrl" @click="openPlagiarismPdfInNewTab">
{{ $t('articleListEditor.plagiarismPreviewOpenTab') }}
</el-button>
</span>
</el-dialog>
<el-dialog
title="Resubmit the manuscript"
:visible.sync="resubmitVisible"
@@ -1743,7 +1839,16 @@ export default {
underReview: ['1'],
finalDecision: ['1'],
is_figure_copyright: '',
figurecopyright_file: ''
figurecopyright_file: '',
plagiarismList: [],
plagiarismListLoading: false,
plagiarismListReady: false,
plagiarismPollTimer: null,
plagiarismSubmitLoading: false,
plagiarismPdfPreviewVisible: false,
plagiarismPdfPreviewUrl: '',
plagiarismPdfPreviewTitle: '',
plagiarismPdfPreviewLoading: false
};
},
async created() {
@@ -1753,6 +1858,16 @@ export default {
this.getWordimgList();
this.getWordTablesList();
this.getFinalList();
this.startPlagiarismPolling();
},
activated() {
this.startPlagiarismPolling(false);
},
deactivated() {
this.stopPlagiarismPolling();
},
beforeDestroy() {
this.stopPlagiarismPolling();
},
computed: {
// coverLetterUrl: function() {
@@ -2857,6 +2972,189 @@ export default {
// 关闭弹窗
closeResubmit() {
(this.resubmitVisible = false), this.$refs['resubmitJournal'].resetFields();
},
startPlagiarismPolling(resetList) {
this.stopPlagiarismPolling();
if (resetList !== false) {
this.plagiarismListReady = false;
this.plagiarismList = [];
}
this.fetchPlagiarismList(false);
this.plagiarismPollTimer = setInterval(() => {
this.fetchPlagiarismList(false);
}, 3 * 60 * 1000);
},
stopPlagiarismPolling() {
if (this.plagiarismPollTimer) {
clearInterval(this.plagiarismPollTimer);
this.plagiarismPollTimer = null;
}
},
async submitPlagiarismCheck() {
const articleId = String((this.editform && this.editform.articleId) || this.$route.query.id || '').trim();
if (!articleId) {
this.$message.warning(this.$t('articleListEditor.plagiarismReportDetailFailed'));
return;
}
this.plagiarismSubmitLoading = true;
try {
const res = await this.$api.post('api/Plagiarism/submit', { article_id: articleId });
if (res && Number(res.code) === 0) {
this.$message.success((res && res.msg) || this.$t('articleListEditor.plagiarismChecking'));
await this.fetchPlagiarismList(true);
} else {
this.$message.error((res && res.msg) || this.$t('articleListEditor.plagiarismCheckFailed'));
}
} catch (e) {
this.$message.error(this.$t('articleListEditor.plagiarismCheckFailed'));
} finally {
this.plagiarismSubmitLoading = false;
}
},
async fetchPlagiarismList(manual) {
const articleId = String((this.editform && this.editform.articleId) || this.$route.query.id || '').trim();
if (!articleId) {
this.plagiarismListReady = true;
return;
}
if (manual && this.plagiarismList.length) {
this.plagiarismListLoading = true;
}
try {
const res = await this.$api.post('api/Plagiarism/getList', { article_id: articleId });
if (res && Number(res.code) === 0) {
const payload = res.data || {};
const list = Array.isArray(payload.list) ? payload.list : Array.isArray(payload) ? payload : [];
this.plagiarismList = list;
} else if (manual) {
this.$message.error((res && res.msg) || this.$t('articleListEditor.plagiarismStatusFailed'));
}
} catch (e) {
if (manual) {
this.$message.error(this.$t('articleListEditor.plagiarismStatusFailed'));
}
} finally {
this.plagiarismListLoading = false;
this.plagiarismListReady = true;
}
},
formatPlagiarismState(state) {
const s = String(state != null ? state : '').trim();
if (!s) return '-';
return s;
},
formatPlagiarismStateLabel(row) {
if (!row || typeof row !== 'object') return '';
const label = String(row.state_label || row.stateLabel || '').trim();
if (label) return label;
const s = row.state;
if (s == null || String(s).trim() === '') return '';
return this.formatPlagiarismState(s);
},
getPlagiarismStateClass(row) {
const s = Number(row && row.state);
if (s === 1) return 'state-uploading';
if (s === 2 || s === 3) return 'state-done';
if (s === 4 || s === 5) return 'state-fail';
return '';
},
getPlagiarismSimilarityScore(row) {
if (!row || typeof row !== 'object') return null;
const raw = row.similarity_score != null ? row.similarity_score : row.similarity;
if (raw == null || String(raw).trim() === '') return null;
const n = Number(raw);
return isNaN(n) ? null : n;
},
formatPlagiarismSimilarity(row) {
const n = this.getPlagiarismSimilarityScore(row);
return n == null ? '' : n + '%';
},
/** Crossref 相似度色块0 绿、129 蓝、30+ 橙 */
getPlagiarismSimilarityLevel(row) {
const n = this.getPlagiarismSimilarityScore(row);
if (n == null) return 'sim-unknown';
if (n <= 0) return 'sim-zero';
if (n < 30) return 'sim-low';
return 'sim-high';
},
formatPlagiarismDate(row) {
if (!row || typeof row !== 'object') return '';
const raw =
row.finish_time ||
row.finished_at ||
row.update_time ||
row.ctime ||
row.create_time ||
row.created_at ||
'';
if (raw == null || String(raw).trim() === '') return '';
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 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 s.length > 10 ? s.slice(0, 10) : 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;
let path = raw.replace(/^\/+/, '');
if (!/^public\//i.test(path)) {
const media = String(this.mediaUrl || '/public/').replace(/\/+$/, '');
if (/^https?:\/\//i.test(media)) {
return media + '/' + path;
}
path = (media.startsWith('/') ? media : '/' + media) + '/' + path;
} else {
path = '/' + path;
}
path = path.replace(/\/+/g, '/');
if (typeof window !== 'undefined' && window.location && window.location.origin && path.startsWith('/')) {
return window.location.origin + path;
}
return path;
},
hasPlagiarismPdf(row) {
return !!this.resolvePlagiarismPdfUrl(row);
},
openPlagiarismReportPage(row) {
const url = this.resolvePlagiarismPdfUrl(row);
if (!url) return;
window.open(url, '_blank', 'noopener,noreferrer');
},
openPlagiarismPdfPreview(row) {
const url = this.resolvePlagiarismPdfUrl(row);
if (!url) return;
const name = row && row.source_file_name ? String(row.source_file_name) : 'report.pdf';
const id = row && row.check_id != null ? row.check_id : '';
this.plagiarismPdfPreviewTitle =
this.$t('articleListEditor.plagiarismPreviewPdf') + (id ? ' #' + id : '') + ' - ' + name;
this.plagiarismPdfPreviewUrl = url;
this.plagiarismPdfPreviewLoading = true;
this.plagiarismPdfPreviewVisible = true;
},
onPlagiarismPdfPreviewClosed() {
this.plagiarismPdfPreviewUrl = '';
this.plagiarismPdfPreviewTitle = '';
this.plagiarismPdfPreviewLoading = false;
},
openPlagiarismPdfInNewTab() {
if (!this.plagiarismPdfPreviewUrl) return;
window.open(this.plagiarismPdfPreviewUrl, '_blank', 'noopener');
}
},
mounted() {
@@ -2921,6 +3219,169 @@ export default {
text-decoration: underline;
}
.plagiarism-check-shell {
margin: 0 0 10px 0;
}
.plagiarism-check-block >>> .el-loading-mask {
background-color: transparent !important;
}
.plagiarism-auto-check-btn.el-button {
display: block;
width: 200px;
border: none;
color: #fff;
font-size: 13px;
margin-left: 75px;
font-weight: 500;
letter-spacing: 0.3px;
padding: 11px 20px;
border-radius: 4px;
background: linear-gradient(135deg, #2ec4b6 0%, #0d9b8f 45%, #0a7f76 100%);
box-shadow: 0 2px 8px rgba(13, 155, 143, 0.35);
transition: opacity 0.2s ease, box-shadow 0.2s ease;
}
.plagiarism-auto-check-btn.el-button:hover,
.plagiarism-auto-check-btn.el-button:focus {
color: #fff;
background: linear-gradient(135deg, #3dd4c6 0%, #14b0a3 45%, #0e948a 100%);
box-shadow: 0 4px 12px rgba(13, 155, 143, 0.45);
}
.plagiarism-auto-check-btn.el-button.is-loading {
background: linear-gradient(135deg, #2ec4b6 0%, #0d9b8f 45%, #0a7f76 100%);
}
.plagiarism-check-block {
margin: 0 0 10px 0;
padding: 10px 12px;
background: #f8fafc;
border: 1px solid #e8edf3;
border-radius: 4px;
}
.plagiarism-check-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 8px;
}
.plagiarism-check-actions {
display: flex;
align-items: center;
gap: 4px;
}
.plagiarism-check-actions .el-button {
padding: 0 6px;
}
.plagiarism-check-title {
font-size: 13px;
color: #606266;
font-weight: 500;
}
.plagiarism-check-list-wrap {
min-height: 32px;
}
.plagiarism-check-row {
display: flex;
align-items: center;
gap: 10px;
padding: 6px 0;
border-top: 1px solid #eef1f5;
font-size: 12px;
line-height: 20px;
}
.plagiarism-check-row:first-child {
border-top: none;
padding-top: 0;
}
.plagiarism-sim-dot {
flex-shrink: 0;
width: 10px;
height: 10px;
border-radius: 1px;
}
.plagiarism-sim-dot.sim-zero {
background: #00a99d;
}
.plagiarism-sim-dot.sim-low {
background: #0070c0;
}
.plagiarism-sim-dot.sim-high {
background: #c87f0a;
}
.plagiarism-sim-dot.sim-unknown {
background: #c0c4cc;
}
.plagiarism-sim-pct {
flex-shrink: 0;
min-width: 36px;
font-weight: 600;
}
.plagiarism-sim-pct.sim-zero {
color: #00a99d;
}
.plagiarism-sim-pct.sim-low {
color: #0070c0;
}
.plagiarism-sim-pct.sim-high {
color: #c87f0a;
}
.plagiarism-sim-pct.sim-unknown {
color: #909399;
}
.plagiarism-sim-date {
flex-shrink: 0;
color: #909399;
}
.plagiarism-sim-state {
flex: 1;
min-width: 48px;
color: #909399;
text-align: center;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.plagiarism-sim-state.state-uploading {
color: #e6a23c;
}
.plagiarism-sim-state.state-done {
color: #67c23a;
}
.plagiarism-sim-state.state-fail {
color: #f56c6c;
}
.plagiarism-sim-report {
flex-shrink: 0;
display: inline-flex;
align-items: center;
justify-content: flex-end;
gap: 6px;
}
.plagiarism-report-preview {
color: #409eff;
text-decoration: none;
display: inline-flex;
align-items: center;
gap: 4px;
}
.plagiarism-report-preview:hover {
text-decoration: underline;
color: #66b1ff;
}
.plagiarism-report-preview .el-icon-link {
font-size: 14px;
}
.plagiarism-check-no-pdf {
color: #f56c6c;
font-size: 12px;
}
.plagiarism-pdf-preview-body {
min-height: 70vh;
}
.plagiarism-pdf-preview-iframe {
width: 100%;
height: 70vh;
border: none;
background: #f5f7fa;
}
.el-upload__tip {
display: inline-block;
line-height: 32px;

View File

@@ -432,6 +432,14 @@
count: counts.young,
jump: { path: '/youthList', queryKey: 'journal_id' }
},
{
value: '6',
label: this.$t('autoPromotion.factoryExpertReviewer'),
desc: this.$t('autoPromotion.factoryExpertReviewer'),
icon: 'el-icon-s-check',
count: counts.reviewer,
jump: { path: '/reviewerList', queryKey: 'journal_id' }
},
{
value: '4',
label: this.$t('autoPromotion.factoryExpertAuthor'),
@@ -447,14 +455,6 @@
icon: 'el-icon-collection',
count: counts.expertDb,
jump: { path: '/expertDatabase', queryKey: 'journal_id' }
},
{
value: '6',
label: this.$t('autoPromotion.factoryExpertReviewer'),
desc: this.$t('autoPromotion.factoryExpertReviewer'),
icon: 'el-icon-s-check',
count: counts.reviewer,
jump: { path: '/reviewerList', queryKey: 'journal_id' }
}
];
},