提交
This commit is contained in:
@@ -47,6 +47,13 @@ const en = {
|
||||
status: 'Status',
|
||||
delete: 'Delete',
|
||||
deleteInfo: 'Are you sure you want to delete this journal installment?',
|
||||
plagiarismNotChecked: 'Not checked',
|
||||
plagiarismChecking: 'Checking…',
|
||||
plagiarismRecheck: '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.',
|
||||
},
|
||||
menu: {
|
||||
main: 'Personal Center',
|
||||
|
||||
@@ -45,6 +45,13 @@ const zh = {
|
||||
status: '状态',
|
||||
delete: '删除',
|
||||
deleteInfo: '您确定要删除该期刊分期吗?',
|
||||
plagiarismNotChecked: '未检测',
|
||||
plagiarismChecking: '正在检测…',
|
||||
plagiarismRecheck: '重新查重',
|
||||
plagiarismCheckFailed: '查重任务启动失败。',
|
||||
plagiarismStatusFailed: '获取查重状态失败。',
|
||||
plagiarismNoReportUrl: '报告链接暂不可用。',
|
||||
plagiarismReportDetailFailed: '获取稿件详情失败,请稍后重试。',
|
||||
},
|
||||
menu: {
|
||||
main: '个人中心',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<div>
|
||||
<div class="article-detail-editor-page">
|
||||
<div class="crumbs">
|
||||
<div class="art_state_message_id" style="padding-left: 18px">
|
||||
<font
|
||||
@@ -915,9 +915,38 @@
|
||||
}}</b>
|
||||
<!-- <el-button type="text" @click="testedit" icon="el-icon-edit">Change</el-button> -->
|
||||
</div>
|
||||
<div>
|
||||
<span>Repetition : </span>
|
||||
<b>{{ form.repetition }}%</b>
|
||||
<div class="detail-plagiarism-row">
|
||||
<span class="detail-plagiarism-main">
|
||||
<span class="detail-plagiarism-lbl">Repetition :</span>
|
||||
<template v-if="detailPlagiarismUiShowProcessing()">
|
||||
<span class="detail-plagiarism-loading">
|
||||
<i class="el-icon-loading"></i>
|
||||
{{ $t('articleListEditor.plagiarismChecking') }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="detailPlagiarismUiShowResult()">
|
||||
<span
|
||||
class="detail-plagiarism-pct-text"
|
||||
:style="detailPlagiarismSimilarityStyle()"
|
||||
@click.stop="detailOpenPlagiarismReport"
|
||||
>
|
||||
{{ detailPlagiarismSimilarityNumber() }}%
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="detail-plagiarism-not-checked-text" @click.stop="detailTriggerCrossrefPlagiarismCheck">
|
||||
{{ $t('articleListEditor.plagiarismNotChecked') }}
|
||||
</span>
|
||||
</template>
|
||||
<el-button
|
||||
v-if="detailPlagiarismShowRecheck()"
|
||||
type="text"
|
||||
class="detail-plagiarism-recheck-btn detail-plagiarism-recheck-inline"
|
||||
@click.stop="detailTriggerCrossrefPlagiarismCheck"
|
||||
>
|
||||
{{ $t('articleListEditor.plagiarismRecheck') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</div>
|
||||
<!--<br clear="both">
|
||||
<el-button type="primary" @click="showResubmit" style="margin: 15px 0 0 0;">Resubmit the manuscript
|
||||
@@ -937,9 +966,38 @@
|
||||
}}</b>
|
||||
<el-button style="padding: 0" type="text" @click="testedit" icon="el-icon-edit">Change</el-button>
|
||||
</div>
|
||||
<div>
|
||||
<span>Repetition : </span>
|
||||
<b>{{ form.repetition }}%</b>
|
||||
<div class="detail-plagiarism-row detail-plagiarism-row--with-actions">
|
||||
<span class="detail-plagiarism-main">
|
||||
<span class="detail-plagiarism-lbl">Repetition :</span>
|
||||
<template v-if="detailPlagiarismUiShowProcessing()">
|
||||
<span class="detail-plagiarism-loading">
|
||||
<i class="el-icon-loading"></i>
|
||||
{{ $t('articleListEditor.plagiarismChecking') }}
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="detailPlagiarismUiShowResult()">
|
||||
<span
|
||||
class="detail-plagiarism-pct-text"
|
||||
:style="detailPlagiarismSimilarityStyle()"
|
||||
@click.stop="detailOpenPlagiarismReport"
|
||||
>
|
||||
{{ detailPlagiarismSimilarityNumber() }}%
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="detail-plagiarism-not-checked-text" @click.stop="detailTriggerCrossrefPlagiarismCheck">
|
||||
{{ $t('articleListEditor.plagiarismNotChecked') }}
|
||||
</span>
|
||||
</template>
|
||||
<el-button
|
||||
v-if="detailPlagiarismShowRecheck()"
|
||||
type="text"
|
||||
class="detail-plagiarism-recheck-btn detail-plagiarism-recheck-inline"
|
||||
@click.stop="detailTriggerCrossrefPlagiarismCheck"
|
||||
>
|
||||
{{ $t('articleListEditor.plagiarismRecheck') }}
|
||||
</el-button>
|
||||
</span>
|
||||
<a :href="mediaUrl + form.repeurl" v-if="form.repeurl" class="zip_load" target="_blank">
|
||||
<img src="../../assets/img/icon_0.png" />
|
||||
<span>Duplicate check file</span>
|
||||
@@ -1426,6 +1484,8 @@
|
||||
import timetalk from './time_talk';
|
||||
import reviewerDetail from '../../components/page/components/articleDetail/reviewerdetail.vue';
|
||||
import FigureCopyright from '../../components/page/components/articleDetail/FigureCopyright.vue';
|
||||
import axios from 'axios';
|
||||
import { getSimilarityStyle } from '../../utils/ithenticateSimilarityStyle';
|
||||
export default {
|
||||
components: {
|
||||
timetalk,
|
||||
@@ -1549,6 +1609,9 @@ export default {
|
||||
approval_content: '',
|
||||
is_figure_copyright: '',
|
||||
repetition: '',
|
||||
plagiarism_similarity: '',
|
||||
plagiarism_report_url: '',
|
||||
plagiarism_job_state: '',
|
||||
manuscirpt: '',
|
||||
remarks: '',
|
||||
state: '',
|
||||
@@ -1743,7 +1806,9 @@ export default {
|
||||
underReview: ['1'],
|
||||
finalDecision: ['1'],
|
||||
is_figure_copyright: '',
|
||||
figurecopyright_file: ''
|
||||
figurecopyright_file: '',
|
||||
plagiarismDetailPollTimer: null,
|
||||
plagiarismDetailPending: false
|
||||
};
|
||||
},
|
||||
async created() {
|
||||
@@ -2541,6 +2606,26 @@ export default {
|
||||
this.form.approval_file = res.article.approval_file;
|
||||
this.form.approval_content = res.article.approval_content;
|
||||
this.form.repetition = res.article.repetition;
|
||||
this.$set(
|
||||
this.form,
|
||||
'plagiarism_similarity',
|
||||
res.article.plagiarism_similarity != null && res.article.plagiarism_similarity !== ''
|
||||
? res.article.plagiarism_similarity
|
||||
: res.article.crossref_similarity != null && res.article.crossref_similarity !== ''
|
||||
? res.article.crossref_similarity
|
||||
: ''
|
||||
);
|
||||
this.$set(
|
||||
this.form,
|
||||
'plagiarism_report_url',
|
||||
res.article.plagiarism_report_url || res.article.crossref_report_url || ''
|
||||
);
|
||||
this.$set(
|
||||
this.form,
|
||||
'plagiarism_job_state',
|
||||
res.article.plagiarism_job_state || res.article.crossref_status || ''
|
||||
);
|
||||
this.detailInitPlagiarismAfterLoad();
|
||||
this.form.remarks = res.article.remarks;
|
||||
this.form.repeurl = res.article.repeurl;
|
||||
this.repeform.repefen = res.article.repetition;
|
||||
@@ -2856,11 +2941,188 @@ export default {
|
||||
// 关闭弹窗
|
||||
closeResubmit() {
|
||||
(this.resubmitVisible = false), this.$refs['resubmitJournal'].resetFields();
|
||||
},
|
||||
|
||||
/* ---------- Crossref plagiarism(详情页右侧) ---------- */
|
||||
detailPlagiarismArticleKey() {
|
||||
return String(this.form.articleId || this.editform.articleId || this.$route.query.id || '');
|
||||
},
|
||||
detailNormalizePlagiarismStatusPayload(body) {
|
||||
const root = body && typeof body === 'object' ? body : {};
|
||||
if (root.code != null && Number(root.code) !== 0) {
|
||||
return { status: 'api_error', similarity: null, reportUrl: '' };
|
||||
}
|
||||
let d = root.data != null ? root.data : root;
|
||||
if (typeof d === 'string') {
|
||||
try {
|
||||
d = JSON.parse(d);
|
||||
} catch (e) {
|
||||
d = {};
|
||||
}
|
||||
}
|
||||
if (!d || typeof d !== 'object') d = {};
|
||||
const statusRaw = d.status || d.state || d.job_status || d.plagiarism_status || '';
|
||||
const status = String(statusRaw).toLowerCase();
|
||||
let sim =
|
||||
d.similarity != null
|
||||
? d.similarity
|
||||
: d.percent != null
|
||||
? d.percent
|
||||
: d.similarity_percent != null
|
||||
? d.similarity_percent
|
||||
: d.crossref_similarity != null
|
||||
? d.crossref_similarity
|
||||
: null;
|
||||
if (sim === '' || sim === undefined) sim = null;
|
||||
const reportUrl = d.report_url || d.reportUrl || d.url || d.report_link || '';
|
||||
return { status, similarity: sim, reportUrl: String(reportUrl || '') };
|
||||
},
|
||||
detailPlagiarismSimilarityRaw() {
|
||||
const f = this.form;
|
||||
if (!f) return null;
|
||||
if (f.plagiarism_similarity != null && f.plagiarism_similarity !== '') return f.plagiarism_similarity;
|
||||
if (f.crossref_similarity != null && f.crossref_similarity !== '') return f.crossref_similarity;
|
||||
const st = String(f.plagiarism_job_state || f.crossref_status || '').toLowerCase();
|
||||
if (['completed', 'done', 'success', 'complete'].includes(st) && (f.plagiarism_similarity === 0 || f.plagiarism_similarity === '0')) {
|
||||
return 0;
|
||||
}
|
||||
const rep = f.repetition;
|
||||
if (rep != null && rep !== '' && Number(rep) > 0) return rep;
|
||||
return null;
|
||||
},
|
||||
detailPlagiarismSimilarityNumber() {
|
||||
const n = this.detailPlagiarismSimilarityRaw();
|
||||
if (n == null || n === '') return 0;
|
||||
const x = Number(n);
|
||||
return isNaN(x) ? 0 : Math.round(x * 10) / 10;
|
||||
},
|
||||
detailPlagiarismUiShowProcessing() {
|
||||
if (this.form._plagiarismLocalLoading) return true;
|
||||
if (this.plagiarismDetailPending) return true;
|
||||
const st = String(this.form.plagiarism_job_state || this.form.crossref_status || '').toLowerCase();
|
||||
return ['pending', 'processing', 'queued', 'running', 'submitted'].includes(st);
|
||||
},
|
||||
detailPlagiarismUiShowResult() {
|
||||
if (this.detailPlagiarismUiShowProcessing()) return false;
|
||||
const st = String(this.form.plagiarism_job_state || this.form.crossref_status || '').toLowerCase();
|
||||
const done = ['completed', 'done', 'success', 'complete'].includes(st);
|
||||
const raw = this.detailPlagiarismSimilarityRaw();
|
||||
if (raw != null && raw !== '' && !isNaN(Number(raw))) {
|
||||
if (Number(raw) === 0 && !done && !(this.form.plagiarism_report_url || this.form.crossref_report_url)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (done && (this.form.plagiarism_similarity === 0 || this.form.plagiarism_similarity === '0')) return true;
|
||||
return !!(this.form.plagiarism_report_url || this.form.crossref_report_url);
|
||||
},
|
||||
detailPlagiarismSimilarityStyle() {
|
||||
const n = Number(this.detailPlagiarismSimilarityNumber());
|
||||
return { color: getSimilarityStyle(n).color };
|
||||
},
|
||||
detailPlagiarismShowRecheck() {
|
||||
if (this.detailPlagiarismUiShowProcessing()) return false;
|
||||
return Number(this.form.state) === 6;
|
||||
},
|
||||
detailInitPlagiarismAfterLoad() {
|
||||
const st = String(this.form.plagiarism_job_state || '').toLowerCase();
|
||||
if (['pending', 'processing', 'queued', 'running', 'submitted'].includes(st)) {
|
||||
this.plagiarismDetailPending = true;
|
||||
this.detailEnsurePlagiarismPoll();
|
||||
}
|
||||
},
|
||||
detailEnsurePlagiarismPoll() {
|
||||
if (!this.plagiarismDetailPending) {
|
||||
this.detailStopPlagiarismPolling();
|
||||
return;
|
||||
}
|
||||
if (!this.plagiarismDetailPollTimer) {
|
||||
this.detailPollPlagiarismOnce();
|
||||
this.plagiarismDetailPollTimer = setInterval(() => this.detailPollPlagiarismOnce(), 60000);
|
||||
}
|
||||
},
|
||||
detailStopPlagiarismPolling() {
|
||||
if (this.plagiarismDetailPollTimer) {
|
||||
clearInterval(this.plagiarismDetailPollTimer);
|
||||
this.plagiarismDetailPollTimer = null;
|
||||
}
|
||||
},
|
||||
async detailPollPlagiarismOnce() {
|
||||
const key = this.detailPlagiarismArticleKey();
|
||||
if (!key || !this.plagiarismDetailPending) return;
|
||||
try {
|
||||
const res = await axios.get('/api/plagiarism/status', { params: { article_id: key } });
|
||||
const body = res && res.data;
|
||||
const norm = this.detailNormalizePlagiarismStatusPayload(body);
|
||||
if (norm.status === 'api_error') {
|
||||
this.plagiarismDetailPending = false;
|
||||
this.detailStopPlagiarismPolling();
|
||||
return;
|
||||
}
|
||||
if (norm.similarity != null && norm.similarity !== '') {
|
||||
this.$set(this.form, 'plagiarism_similarity', norm.similarity);
|
||||
}
|
||||
if (norm.reportUrl) this.$set(this.form, 'plagiarism_report_url', norm.reportUrl);
|
||||
if (norm.status) this.$set(this.form, 'plagiarism_job_state', norm.status);
|
||||
const active = ['pending', 'processing', 'queued', 'running', 'submitted'];
|
||||
const terminal = ['completed', 'done', 'success', 'complete', 'failed', 'error', 'fail', 'cancelled'];
|
||||
const isActive = active.includes(norm.status);
|
||||
let clearPending =
|
||||
terminal.includes(norm.status) ||
|
||||
norm.status === 'error' ||
|
||||
(!isActive && norm.reportUrl && norm.similarity != null && norm.similarity !== '');
|
||||
if (clearPending) {
|
||||
this.plagiarismDetailPending = false;
|
||||
}
|
||||
this.detailEnsurePlagiarismPoll();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.$message.error(this.$t('articleListEditor.plagiarismStatusFailed'));
|
||||
}
|
||||
},
|
||||
async detailTriggerCrossrefPlagiarismCheck() {
|
||||
const key = this.detailPlagiarismArticleKey();
|
||||
if (!key) return;
|
||||
if (this.detailPlagiarismUiShowProcessing()) return;
|
||||
this.$set(this.form, '_plagiarismLocalLoading', true);
|
||||
try {
|
||||
const res = await this.$api.post('api/plagiarism/check', { article_id: key });
|
||||
if (res && Number(res.code) === 0) {
|
||||
this.plagiarismDetailPending = true;
|
||||
this.$set(this.form, 'plagiarism_job_state', 'pending');
|
||||
this.detailEnsurePlagiarismPoll();
|
||||
await this.detailPollPlagiarismOnce();
|
||||
} else {
|
||||
this.$message.error((res && res.msg) || this.$t('articleListEditor.plagiarismCheckFailed'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.$message.error(this.$t('articleListEditor.plagiarismCheckFailed'));
|
||||
} finally {
|
||||
this.$set(this.form, '_plagiarismLocalLoading', false);
|
||||
}
|
||||
},
|
||||
detailOpenPlagiarismReport() {
|
||||
const raw = (this.form.plagiarism_report_url || this.form.crossref_report_url || '').trim();
|
||||
if (!raw) {
|
||||
this.$message.warning(this.$t('articleListEditor.plagiarismNoReportUrl'));
|
||||
return;
|
||||
}
|
||||
let full = raw;
|
||||
if (!/^https?:\/\//i.test(raw)) {
|
||||
const base = (this.mediaUrl || '').replace(/\/+$/, '');
|
||||
const path = raw.replace(/^\/+/, '');
|
||||
full = base ? `${base}/${path}` : `/${path}`;
|
||||
}
|
||||
window.open(full, '_blank');
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.opname = this.$route.query.mark;
|
||||
this.resubmitJournal.manuscriptId = this.$route.query.id;
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.detailStopPlagiarismPolling();
|
||||
}
|
||||
};
|
||||
</script>
|
||||
@@ -3345,4 +3607,80 @@ td {
|
||||
.copyright-declaration-wrapper :deep(.el-radio.is-checked .el-radio__label) {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* 详情页:去掉全局 main.css 中 .art_caozuo_ 的浅蓝底;查重行无额外底色 */
|
||||
.article-detail-editor-page .art_caozuo_ {
|
||||
background-color: #fff !important;
|
||||
border: 1px solid #ebeef5 !important;
|
||||
}
|
||||
.detail-plagiarism-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 6px 8px;
|
||||
background: transparent !important;
|
||||
padding: 4px 0 8px 0;
|
||||
margin: 0 0 10px 0;
|
||||
}
|
||||
/* 合并「Repetition :」与数值,避免命中 .art_caozuo_ > div > span 的 min-width:65px 造成巨大空隙 */
|
||||
.article-detail-editor-page .art_caozuo_ > div > span.detail-plagiarism-main {
|
||||
min-width: 0 !important;
|
||||
width: auto !important;
|
||||
max-width: 100%;
|
||||
margin-right: 8px !important;
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
flex-wrap: wrap;
|
||||
gap: 2px 6px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
.article-detail-editor-page .art_caozuo_ .detail-plagiarism-lbl {
|
||||
color: #777;
|
||||
margin: 0 2px 0 0 !important;
|
||||
min-width: 0 !important;
|
||||
width: auto !important;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.detail-plagiarism-loading {
|
||||
color: #409eff;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
}
|
||||
.detail-plagiarism-loading .el-icon-loading {
|
||||
margin-right: 6px;
|
||||
}
|
||||
.detail-plagiarism-pct-text {
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
padding: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.detail-plagiarism-pct-text:hover {
|
||||
opacity: 0.92;
|
||||
}
|
||||
.detail-plagiarism-not-checked-text {
|
||||
cursor: pointer;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #409eff;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
.detail-plagiarism-not-checked-text:hover {
|
||||
color: #66b1ff;
|
||||
}
|
||||
.detail-plagiarism-recheck-btn {
|
||||
margin-left: 4px !important;
|
||||
font-weight: 600 !important;
|
||||
}
|
||||
/* 紧跟在百分比 / 未检测 文案后,避免继承整行大间距 */
|
||||
.detail-plagiarism-main .detail-plagiarism-recheck-inline.el-button--text {
|
||||
margin-left: 0 !important;
|
||||
padding: 0 2px !important;
|
||||
vertical-align: baseline;
|
||||
line-height: inherit;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -116,9 +116,36 @@
|
||||
<i class="el-icon-data-line"></i> Manuscript Tracking
|
||||
</b>
|
||||
|
||||
<span style="float: right">
|
||||
<span style="float: right" class="plagiarism-header-bar">
|
||||
<span class="labelTitle" style="font-weight: 500; font-size: 13px">Plagiarism Check :</span>
|
||||
<font style="margin-right: 16px; font-size: 13px; font-weight: 700"> {{ item.repetition }} % </font>
|
||||
<template v-if="plagiarismUiShowProcessing(item)">
|
||||
<span class="plagiarism-inline-loading plagiarism-on-blue">
|
||||
<i class="el-icon-loading"></i>
|
||||
<span>{{ $t('articleListEditor.plagiarismChecking') }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template v-else-if="plagiarismUiShowResult(item)">
|
||||
<span
|
||||
class="plagiarism-pct-text plagiarism-on-blue"
|
||||
@click.stop="openPlagiarismReport(item)"
|
||||
>
|
||||
{{ plagiarismSimilarityNumber(item) }}%
|
||||
</span>
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="plagiarism-not-checked-text plagiarism-on-blue" @click.stop="triggerCrossrefPlagiarismCheck(item)">
|
||||
{{ $t('articleListEditor.plagiarismNotChecked') }}
|
||||
</span>
|
||||
</template>
|
||||
<el-button
|
||||
v-if="plagiarismShowRecheck(item)"
|
||||
type="text"
|
||||
size="mini"
|
||||
class="plagiarism-recheck-btn"
|
||||
@click.native.stop="triggerCrossrefPlagiarismCheck(item)"
|
||||
>
|
||||
{{ $t('articleListEditor.plagiarismRecheck') }}
|
||||
</el-button>
|
||||
</span>
|
||||
<span style="margin: 0 10px; float: right">| </span>
|
||||
|
||||
@@ -1065,6 +1092,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import axios from 'axios';
|
||||
import { Loading } from 'element-ui';
|
||||
import timetalk from './time_talk';
|
||||
import commonRemarkList from './articleListEditor_A_list.vue';
|
||||
@@ -1432,12 +1460,18 @@ export default {
|
||||
editVisible1: false,
|
||||
bankVisible: false,
|
||||
majorData: {},
|
||||
googleSearchInfo: ''
|
||||
googleSearchInfo: '',
|
||||
/** Crossref / plagiarism async polling */
|
||||
plagiarismPollTimer: null,
|
||||
plagiarismPendingIds: {}
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getdate();
|
||||
},
|
||||
beforeDestroy() {
|
||||
this.stopPlagiarismPolling();
|
||||
},
|
||||
computed: {
|
||||
upload_zip: function () {
|
||||
return this.baseUrl + 'api/Article/up_file/type/repezip';
|
||||
@@ -2072,6 +2106,7 @@ export default {
|
||||
this.tableData[i].reportList = this.tableData[i].reportList.slice(0, 3);
|
||||
this.$forceUpdate();
|
||||
}
|
||||
this.initPlagiarismFromList();
|
||||
|
||||
loading.close();
|
||||
})
|
||||
@@ -2658,6 +2693,234 @@ export default {
|
||||
return str;
|
||||
},
|
||||
|
||||
/* ---------- Crossref plagiarism (list header) ---------- */
|
||||
_plagiarismArticleKey(item) {
|
||||
return item && item.article_id != null ? String(item.article_id) : '';
|
||||
},
|
||||
_plagiarismPending(articleKey) {
|
||||
return !!(articleKey && this.plagiarismPendingIds[articleKey]);
|
||||
},
|
||||
plagiarismSimilarityNumber(item) {
|
||||
const n = this.plagiarismSimilarityRaw(item);
|
||||
if (n == null || n === '') return 0;
|
||||
const x = Number(n);
|
||||
return isNaN(x) ? 0 : Math.round(x * 10) / 10;
|
||||
},
|
||||
plagiarismSimilarityRaw(item) {
|
||||
if (!item) return null;
|
||||
if (item.plagiarism_similarity != null && item.plagiarism_similarity !== '') return item.plagiarism_similarity;
|
||||
if (item.crossref_similarity != null && item.crossref_similarity !== '') return item.crossref_similarity;
|
||||
const st = String(item.plagiarism_job_state || item.crossref_status || '').toLowerCase();
|
||||
if (['completed', 'done', 'success', 'complete'].includes(st) && (item.plagiarism_similarity === 0 || item.plagiarism_similarity === '0')) {
|
||||
return 0;
|
||||
}
|
||||
const rep = item.repetition;
|
||||
if (rep != null && rep !== '' && Number(rep) > 0) return rep;
|
||||
return null;
|
||||
},
|
||||
plagiarismUiShowProcessing(item) {
|
||||
const key = this._plagiarismArticleKey(item);
|
||||
if (!key) return false;
|
||||
if (item._plagiarismLocalLoading) return true;
|
||||
if (this._plagiarismPending(key)) return true;
|
||||
const st = String(item.plagiarism_job_state || item.crossref_status || '').toLowerCase();
|
||||
return ['pending', 'processing', 'queued', 'running', 'submitted'].includes(st);
|
||||
},
|
||||
plagiarismUiShowResult(item) {
|
||||
if (this.plagiarismUiShowProcessing(item)) return false;
|
||||
const st = String(item.plagiarism_job_state || item.crossref_status || '').toLowerCase();
|
||||
const done = ['completed', 'done', 'success', 'complete'].includes(st);
|
||||
const raw = this.plagiarismSimilarityRaw(item);
|
||||
if (raw != null && raw !== '' && !isNaN(Number(raw))) {
|
||||
if (Number(raw) === 0 && !done && !(item.plagiarism_report_url || item.crossref_report_url)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if (done && (item.plagiarism_similarity === 0 || item.plagiarism_similarity === '0')) return true;
|
||||
return !!(item.plagiarism_report_url || item.crossref_report_url);
|
||||
},
|
||||
plagiarismShowRecheck(item) {
|
||||
if (this.plagiarismUiShowProcessing(item)) return false;
|
||||
return Number(item.state) === 6;
|
||||
},
|
||||
initPlagiarismFromList() {
|
||||
if (!this.tableData || !this.tableData.length) {
|
||||
this.reconcilePlagiarismPollTimer();
|
||||
return;
|
||||
}
|
||||
this.tableData.forEach((row) => {
|
||||
if (row.crossref_report_url && !row.plagiarism_report_url) {
|
||||
this.$set(row, 'plagiarism_report_url', row.crossref_report_url);
|
||||
}
|
||||
if (row.crossref_similarity != null && row.plagiarism_similarity == null) {
|
||||
this.$set(row, 'plagiarism_similarity', row.crossref_similarity);
|
||||
}
|
||||
if (row.crossref_status && !row.plagiarism_job_state) {
|
||||
this.$set(row, 'plagiarism_job_state', row.crossref_status);
|
||||
}
|
||||
const key = this._plagiarismArticleKey(row);
|
||||
const st = String(row.plagiarism_job_state || '').toLowerCase();
|
||||
if (key && ['pending', 'processing', 'queued', 'running', 'submitted'].includes(st)) {
|
||||
this.$set(this.plagiarismPendingIds, key, true);
|
||||
}
|
||||
});
|
||||
this.reconcilePlagiarismPollTimer();
|
||||
},
|
||||
reconcilePlagiarismPollTimer() {
|
||||
const hasPending = Object.keys(this.plagiarismPendingIds).some((k) => this.plagiarismPendingIds[k]);
|
||||
if (hasPending) {
|
||||
if (!this.plagiarismPollTimer) {
|
||||
this.pollPlagiarismPendingOnce();
|
||||
this.plagiarismPollTimer = setInterval(() => this.pollPlagiarismPendingOnce(), 60000);
|
||||
}
|
||||
} else if (this.plagiarismPollTimer) {
|
||||
clearInterval(this.plagiarismPollTimer);
|
||||
this.plagiarismPollTimer = null;
|
||||
}
|
||||
},
|
||||
stopPlagiarismPolling() {
|
||||
if (this.plagiarismPollTimer) {
|
||||
clearInterval(this.plagiarismPollTimer);
|
||||
this.plagiarismPollTimer = null;
|
||||
}
|
||||
this.plagiarismPendingIds = {};
|
||||
},
|
||||
pollPlagiarismPendingOnce() {
|
||||
Object.keys(this.plagiarismPendingIds).forEach((key) => {
|
||||
if (this.plagiarismPendingIds[key]) this.fetchPlagiarismStatusByArticleId(key);
|
||||
});
|
||||
},
|
||||
_normalizePlagiarismStatusPayload(body) {
|
||||
const root = body && typeof body === 'object' ? body : {};
|
||||
if (root.code != null && Number(root.code) !== 0) {
|
||||
return { status: 'api_error', similarity: null, reportUrl: '' };
|
||||
}
|
||||
let d = root.data != null ? root.data : root;
|
||||
if (typeof d === 'string') {
|
||||
try {
|
||||
d = JSON.parse(d);
|
||||
} catch (e) {
|
||||
d = {};
|
||||
}
|
||||
}
|
||||
if (!d || typeof d !== 'object') d = {};
|
||||
const statusRaw = d.status || d.state || d.job_status || d.plagiarism_status || '';
|
||||
const status = String(statusRaw).toLowerCase();
|
||||
let sim =
|
||||
d.similarity != null
|
||||
? d.similarity
|
||||
: d.percent != null
|
||||
? d.percent
|
||||
: d.similarity_percent != null
|
||||
? d.similarity_percent
|
||||
: d.crossref_similarity != null
|
||||
? d.crossref_similarity
|
||||
: null;
|
||||
if (sim === '' || sim === undefined) sim = null;
|
||||
const reportUrl = d.report_url || d.reportUrl || d.url || d.report_link || '';
|
||||
return { status, similarity: sim, reportUrl: String(reportUrl || '') };
|
||||
},
|
||||
findTableRowByArticleId(articleId) {
|
||||
const id = String(articleId);
|
||||
return (this.tableData || []).find((r) => String(r.article_id) === id) || null;
|
||||
},
|
||||
async fetchPlagiarismStatusByArticleId(articleId) {
|
||||
const key = String(articleId);
|
||||
try {
|
||||
const res = await axios.get('/api/plagiarism/status', { params: { article_id: key } });
|
||||
const body = res && res.data;
|
||||
const norm = this._normalizePlagiarismStatusPayload(body);
|
||||
if (norm.status === 'api_error') {
|
||||
this.$set(this.plagiarismPendingIds, key, false);
|
||||
this.reconcilePlagiarismPollTimer();
|
||||
return;
|
||||
}
|
||||
const row = this.findTableRowByArticleId(key);
|
||||
if (row) {
|
||||
if (norm.similarity != null && norm.similarity !== '') {
|
||||
this.$set(row, 'plagiarism_similarity', norm.similarity);
|
||||
}
|
||||
if (norm.reportUrl) this.$set(row, 'plagiarism_report_url', norm.reportUrl);
|
||||
if (norm.status) this.$set(row, 'plagiarism_job_state', norm.status);
|
||||
}
|
||||
const active = ['pending', 'processing', 'queued', 'running', 'submitted'];
|
||||
const terminal = ['completed', 'done', 'success', 'complete', 'failed', 'error', 'fail', 'cancelled'];
|
||||
const isActive = active.includes(norm.status);
|
||||
let clearPending =
|
||||
terminal.includes(norm.status) ||
|
||||
norm.status === 'error' ||
|
||||
(!isActive && norm.reportUrl && norm.similarity != null && norm.similarity !== '');
|
||||
if (clearPending) {
|
||||
this.$set(this.plagiarismPendingIds, key, false);
|
||||
}
|
||||
this.reconcilePlagiarismPollTimer();
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.$message.error(this.$t('articleListEditor.plagiarismStatusFailed'));
|
||||
}
|
||||
},
|
||||
async triggerCrossrefPlagiarismCheck(item) {
|
||||
const key = this._plagiarismArticleKey(item);
|
||||
if (!key) return;
|
||||
if (this.plagiarismUiShowProcessing(item)) return;
|
||||
this.$set(item, '_plagiarismLocalLoading', true);
|
||||
try {
|
||||
const res = await this.$api.post('api/plagiarism/check', { article_id: key });
|
||||
if (res && Number(res.code) === 0) {
|
||||
this.$set(this.plagiarismPendingIds, key, true);
|
||||
this.$set(item, 'plagiarism_job_state', 'pending');
|
||||
this.reconcilePlagiarismPollTimer();
|
||||
await this.fetchPlagiarismStatusByArticleId(key);
|
||||
} else {
|
||||
this.$message.error((res && res.msg) || this.$t('articleListEditor.plagiarismCheckFailed'));
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.$message.error(this.$t('articleListEditor.plagiarismCheckFailed'));
|
||||
} finally {
|
||||
this.$set(item, '_plagiarismLocalLoading', false);
|
||||
}
|
||||
},
|
||||
async openPlagiarismReport(item) {
|
||||
const articleId = item && item.article_id;
|
||||
if (!articleId) {
|
||||
this.$message.warning(this.$t('articleListEditor.plagiarismNoReportUrl'));
|
||||
return;
|
||||
}
|
||||
let raw = '';
|
||||
try {
|
||||
const res = await this.$api.post('api/Article/getArticleDetail', {
|
||||
articleId,
|
||||
human: 'editor'
|
||||
});
|
||||
console.log("🚀 ~ openPlagiarismReport ~ res:", res);
|
||||
|
||||
const a = res && res.article;
|
||||
if (a) {
|
||||
raw = String(
|
||||
a.repeurl || ''
|
||||
).trim();
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
this.$message.error(this.$t('articleListEditor.plagiarismReportDetailFailed'));
|
||||
return;
|
||||
}
|
||||
const url = String(raw).trim();
|
||||
if (!url) {
|
||||
this.$message.warning(this.$t('articleListEditor.plagiarismNoReportUrl'));
|
||||
return;
|
||||
}
|
||||
let full = url;
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
const base = (this.mediaUrl || '').replace(/\/+$/, '');
|
||||
const path = url.replace(/^\/+/, '');
|
||||
full = base ? `${base}/${path}` : `/${path}`;
|
||||
}
|
||||
window.open(full, '_blank');
|
||||
},
|
||||
|
||||
//文章类型
|
||||
|
||||
|
||||
@@ -3426,4 +3689,65 @@ td {
|
||||
border-bottom: 1px solid #f0f0f0;
|
||||
/* box-shadow: 0 2px 8px rgba(0,0,0,0.1); */
|
||||
}
|
||||
|
||||
/* Crossref plagiarism strip (blue header bar) */
|
||||
.plagiarism-header-bar {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px 8px;
|
||||
margin-right: 4px;
|
||||
max-width: 48%;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.plagiarism-on-blue {
|
||||
vertical-align: middle;
|
||||
}
|
||||
.articleTopBaseInfo .plagiarism-inline-loading {
|
||||
color: #e6f4ff;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.articleTopBaseInfo .plagiarism-inline-loading .el-icon-loading {
|
||||
font-size: 14px;
|
||||
}
|
||||
.articleTopBaseInfo .plagiarism-pct-text {
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 800;
|
||||
color: #fff;
|
||||
background: none !important;
|
||||
border: none !important;
|
||||
padding: 0;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.articleTopBaseInfo .plagiarism-pct-text:hover {
|
||||
filter: brightness(1.08);
|
||||
}
|
||||
.articleTopBaseInfo .plagiarism-not-checked-text {
|
||||
cursor: pointer;
|
||||
margin-right: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #e6f4ff;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 2px;
|
||||
}
|
||||
.articleTopBaseInfo .plagiarism-not-checked-text:hover {
|
||||
color: #fff;
|
||||
}
|
||||
.articleTopBaseInfo .plagiarism-recheck-btn {
|
||||
color: #e6f4ff !important;
|
||||
padding: 0 4px !important;
|
||||
font-weight: 600;
|
||||
}
|
||||
.articleTopBaseInfo .plagiarism-recheck-btn:hover {
|
||||
color: #fff !important;
|
||||
text-decoration: underline;
|
||||
}
|
||||
</style>
|
||||
|
||||
15
src/utils/ithenticateSimilarityStyle.js
Normal file
15
src/utils/ithenticateSimilarityStyle.js
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 基于 iThenticate 标准的颜色映射(与编辑端常见分档一致)
|
||||
* @param {number|string} score 相似度百分比(可为小数,内部会规范化)
|
||||
* @returns {{ color: string, label: string }}
|
||||
*/
|
||||
export function getSimilarityStyle(score) {
|
||||
let n = Number(score);
|
||||
if (!Number.isFinite(n) || n < 0) n = 0;
|
||||
const s = Math.round(n);
|
||||
if (s === 0) return { color: '#0000FF', label: 'Blue' };
|
||||
if (s <= 24) return { color: '#008000', label: 'Green' };
|
||||
if (s <= 49) return { color: '#EDBD3E', label: 'Yellow' };
|
||||
if (s <= 74) return { color: '#FF8C00', label: 'Orange' };
|
||||
return { color: '#FF0000', label: 'Red' };
|
||||
}
|
||||
Reference in New Issue
Block a user