文章详情页增加生成produce 的按钮

This commit is contained in:
2026-05-25 10:41:57 +08:00
parent 2e06824edd
commit 73c26045a0
4 changed files with 461 additions and 115 deletions

View File

@@ -1569,6 +1569,8 @@ const en = {
cancel: 'Cancel'
},
refRelevance: {
startDetect: 'Start relevance check',
detecting: 'Detecting…',
filterAll: 'All ({count})',
filterModify: 'Needs revision ({count})',
columnTitle: 'AI citation relevance review',

View File

@@ -1550,6 +1550,8 @@ const zh = {
cancel: '取消'
},
refRelevance: {
startDetect: '开始检测相关性',
detecting: '检测中…',
filterAll: '全部({count}',
filterModify: '需修改({count}',
columnTitle: 'AI 智能评定相关性建议',

View File

@@ -1,6 +1,7 @@
<template>
<div>
<div class="crumbs">
<div class="crumbs-header-bar">
<div class="art_state_message_id" style="padding-left: 18px">
<font
>ID : <b style="margin-right: 25px">{{ form.accept_sn }}</b></font
@@ -188,6 +189,17 @@
</el-popover>
</font>
</div>
<el-button
v-if="showBeginProduceBtn"
size="small"
icon="el-icon-collection-tag"
:loading="beginProduceLoading"
class="crumbs-produce-btn"
@click="beginProduce"
>
Begin Produce
</el-button>
</div>
<div style="position: relative" v-if="currentArticleData.ai_review == ''">
<el-tooltip
class="item"
@@ -1561,6 +1573,7 @@ export default {
items: '',
drawer: false,
loading: false,
beginProduceLoading: false,
loading1: false,
loading2: false,
loading3: false,
@@ -1656,6 +1669,7 @@ export default {
manuscirpt: '',
remarks: '',
state: '',
has_produce: null,
ctime: '',
is_draft: '',
authorList: [],
@@ -1910,6 +1924,11 @@ export default {
}
return frag;
},
showBeginProduceBtn() {
const state = Number(this.form.state);
const hasProduce = Number(this.form.has_produce);
return state === 6 && hasProduce === 0;
},
articleState: function () {
let str = '';
@@ -1951,6 +1970,30 @@ export default {
}
},
methods: {
beginProduce() {
const articleId = this.editform.articleId;
if (!articleId) return;
this.beginProduceLoading = true;
this.$api
.post('api/Production/addProduction', {
article_id: articleId
})
.then((res) => {
if (res.code == 0) {
this.form.has_produce = 1;
this.$message.success('Successfully added production instance!');
} else {
this.$message.error(res.msg || 'Failed to create production instance.');
}
})
.catch((err) => {
this.$message.error(typeof err === 'string' ? err : 'Failed to create production instance.');
console.log(err);
})
.finally(() => {
this.beginProduceLoading = false;
});
},
reOpenDialog() {
// 强制先关闭,确保状态发生位移
this.copyrightDialogVisible = false;
@@ -2670,6 +2713,7 @@ export default {
this.repeform.repefen = res.article.repetition;
this.remark.content = res.article.remarks;
this.form.state = res.article.state;
this.form.has_produce = res.article.has_produce;
this.form.ctime = res.article.ctime;
this.form.is_use_ai = res.article.is_use_ai;
this.form.use_ai_explain = res.article.use_ai_explain;
@@ -3196,6 +3240,40 @@ export default {
</script>
<style scoped>
.crumbs-header-bar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
}
.crumbs-produce-btn.el-button {
flex-shrink: 0;
margin-right: 18px;
border: none;
color: #fff;
font-size: 13px;
font-weight: 600;
letter-spacing: 0.3px;
padding: 10px 18px;
border-radius: 4px;
background: linear-gradient(135deg, #ffb347 0%, #ff7b00 48%, #e8590c 100%);
box-shadow: 0 3px 10px rgba(232, 89, 12, 0.42);
transition: transform 0.2s ease, box-shadow 0.2s ease, opacity 0.2s ease;
}
.crumbs-produce-btn.el-button:hover,
.crumbs-produce-btn.el-button:focus {
color: #fff;
background: linear-gradient(135deg, #ffc46b 0%, #ff922b 48%, #f76707 100%);
box-shadow: 0 5px 14px rgba(232, 89, 12, 0.5);
transform: translateY(-1px);
}
.crumbs-produce-btn.el-button.is-loading {
background: linear-gradient(135deg, #ffb347 0%, #ff7b00 48%, #e8590c 100%);
}
.art_state_ {
padding: 20px 20px 1px 20px;
}

View File

@@ -118,6 +118,16 @@
>Delete</el-button
>
</div>
<div v-if="role == 'editor'" class="ref-table-toolbar-right">
<el-button
type="primary"
:loading="refRelevanceGlobalDetecting"
:disabled="!chanFerForm.length"
@click="startRefRelevanceDetect"
>
{{ $t('refRelevance.startDetect') }}
</el-button>
</div>
<!-- <div v-if="showB_step == 2" class="ref-ai-filter-group">
<el-radio-group v-model="refAiFilter" size="small" @change="onRefAiFilterChange">
<el-radio-button label="all">{{ $t('refRelevance.filterAll', { count: refAiFilterStats.total }) }}</el-radio-button>
@@ -200,7 +210,7 @@
</template>
</el-table-column>
<!-- <el-table-column
<el-table-column
v-if="role == 'editor'"
:label="$t('refRelevance.columnTitle')"
width="340"
@@ -208,7 +218,11 @@
class-name="ref-relevance-column"
>
<template slot-scope="scope">
<div v-if="isRefRelevanceUncited(scope.row)" class="ref-ai-cell ref-ai-cell--uncited">
<div v-if="isRefRowRelevanceDetecting(scope.row)" class="ref-ai-cell ref-ai-cell--loading">
<i class="el-icon-loading ref-ai-loading-icon"></i>
<span>{{ $t('refRelevance.detecting') }}</span>
</div>
<div v-else-if="isRefRelevanceUncited(scope.row)" class="ref-ai-cell ref-ai-cell--uncited">
<span class="ref-ai-tag ref-ai-tag--error">{{ $t('refRelevance.uncitedTag') }}</span>
<p class="ref-ai-uncited-desc">{{ $t('refRelevance.uncitedDesc') }}</p>
<p class="ref-ai-uncited-tip">{{ $t('refRelevance.uncitedTip') }}</p>
@@ -232,9 +246,12 @@
<div class="ref-ai-item-line">
<div class="ref-ai-item-line-main">
<span class="ref-ai-item-loc">{{ $t('refRelevance.citationN', { n: formatRelevanceCiteIndex(item, ri) }) }}</span>
<span v-if="item.score != null" class="ref-ai-item-score">{{
$t('refRelevance.relevancePctShort', { score: item.score })
}}</span>
<span
v-if="item.score != null"
class="ref-ai-item-score"
:class="getRelevanceScoreClass(item)"
>{{ $t('refRelevance.relevancePctShort', { score: item.score }) }}</span
>
<span
class="ref-ai-item-verdict"
:class="'verdict-' + getRelevanceCiteListTone(item)"
@@ -247,7 +264,7 @@
</div>
<span v-else class="ref-ai-empty">—</span>
</template>
</el-table-column> -->
</el-table-column>
<el-table-column align="center" :width="role == 'editor' ? '330' : '290'">
<div slot-scope="scope">
@@ -647,9 +664,12 @@
<span class="ref-ai-item-loc">{{
$t('refRelevance.citationN', { n: formatRelevanceCiteIndex(citeItem, ri) })
}}</span>
<span v-if="citeItem.score != null" class="ref-ai-item-score">{{
$t('refRelevance.relevancePctShort', { score: citeItem.score })
}}</span>
<span
v-if="citeItem.score != null"
class="ref-ai-item-score"
:class="getRelevanceScoreClass(citeItem)"
>{{ $t('refRelevance.relevancePctShort', { score: citeItem.score }) }}</span
>
<span
class="ref-ai-item-verdict"
:class="'verdict-' + getRelevanceCiteListTone(citeItem)"
@@ -784,7 +804,9 @@ export default {
relevanceActiveCiteMark: '',
relevanceScrollTimer: null,
/** 编辑端 AI 相关性列表筛选all | modify */
refAiFilter: 'all'
refAiFilter: 'all',
refRelevanceGlobalDetecting: false,
refRelevanceDetectTimers: []
};
},
computed: {
@@ -866,6 +888,9 @@ export default {
default: null
}
},
beforeDestroy() {
this.clearRefRelevanceDetectTimers();
},
watch: {
article_id(val, oldVal) {
if (val != null && val !== '' && val !== oldVal) {
@@ -1010,29 +1035,126 @@ export default {
return {};
},
init(e) {
this.chanFerForm = this.attachMockRelevanceToRefs(e);
this.clearRefRelevanceDetectTimers();
this.refRelevanceGlobalDetecting = false;
const rows = Array.isArray(e) ? e : [];
if (this.role === 'editor') {
rows.forEach((row) => this.resetRefRowRelevanceState(row));
this.attachMockRelevanceToRefs(rows);
this.applyMockRelevanceDisplayState(rows);
this.$forceUpdate();
}
this.bijiao();
////console.log('更新更新')
},
/** 临时 mock编辑端引用相关性(后端接口就绪后删除 */
/** 临时 mock列表直接展示假数据(后端接口就绪后替换 */
attachMockRelevanceToRefs(list) {
const rows = Array.isArray(list) ? list : [];
if (this.role !== 'editor') return rows;
rows.forEach((row, index) => {
if (!row || typeof row !== 'object') return;
if (Array.isArray(row.ai_relevance_list) && row.ai_relevance_list.length) return;
if (row.ai_relevance_uncited) return;
const list = this.buildMockRelevanceList(index);
if (list.length) {
list.forEach((item) => this.enrichRelevanceAnnotations(item));
row.ai_relevance_list = list;
} else if (index === 3) {
if (index === 4) {
row.ai_relevance_uncited = true;
row.ai_relevance_list = [];
return;
}
const mockList = this.buildMockRelevanceList(index);
if (mockList.length) {
mockList.forEach((item) => this.enrichRelevanceAnnotations(item));
row.ai_relevance_list = mockList;
row.ai_relevance_uncited = false;
}
});
return rows;
},
applyMockRelevanceDisplayState(rows) {
(rows || []).forEach((row) => {
if (!row) return;
row._refAiState = 'done';
const list = row.ai_relevance_list || [];
row._refAiVisibleCount = list.length;
});
},
resetRefRowRelevanceState(row) {
if (!row || typeof row !== 'object') return row;
delete row.ai_relevance_list;
delete row.ai_relevance_uncited;
row._refAiState = 'idle';
row._refAiVisibleCount = 0;
return row;
},
clearRefRelevanceDetectTimers() {
(this.refRelevanceDetectTimers || []).forEach((id) => clearTimeout(id));
this.refRelevanceDetectTimers = [];
},
isRefRowRelevanceDetecting(row) {
return !!(row && row._refAiState === 'detecting');
},
startRefRelevanceDetect() {
if (this.role !== 'editor' || this.refRelevanceGlobalDetecting) return;
const rows = this.chanFerForm || [];
if (!rows.length) return;
this.clearRefRelevanceDetectTimers();
this.refRelevanceGlobalDetecting = true;
rows.forEach((row, index) => {
if (!row) return;
row._refAiState = 'detecting';
row._refAiVisibleCount = 0;
row.ai_relevance_list = [];
row.ai_relevance_uncited = false;
});
this.$forceUpdate();
const rowStagger = 480;
const citeStagger = 320;
rows.forEach((row, index) => {
const t = setTimeout(() => {
this.finishRefRowRelevanceDetect(row, index, citeStagger);
if (index === rows.length - 1) {
const tEnd = setTimeout(() => {
this.refRelevanceGlobalDetecting = false;
}, 400);
this.refRelevanceDetectTimers.push(tEnd);
}
}, index * rowStagger + 520);
this.refRelevanceDetectTimers.push(t);
});
},
finishRefRowRelevanceDetect(row, index, citeStagger) {
if (!row) return;
if (index === 4) {
row.ai_relevance_uncited = true;
row.ai_relevance_list = [];
row._refAiState = 'done';
row._refAiVisibleCount = 0;
this.$forceUpdate();
return;
}
const list = this.buildMockRelevanceList(index);
list.forEach((item) => this.enrichRelevanceAnnotations(item));
row.ai_relevance_list = list;
row.ai_relevance_uncited = false;
row._refAiState = 'done';
row._refAiVisibleCount = list.length;
this.$forceUpdate();
if (list.length && citeStagger > 0) {
row._refAiVisibleCount = 0;
this.revealRefRowCites(row, 0, citeStagger);
}
},
revealRefRowCites(row, count, citeStagger) {
const list = row.ai_relevance_list || [];
if (count >= list.length) return;
row._refAiVisibleCount = count + 1;
this.$forceUpdate();
const t = setTimeout(() => this.revealRefRowCites(row, count + 1, citeStagger), citeStagger);
this.refRelevanceDetectTimers.push(t);
},
getRelevanceScoreClass(item) {
const score = Number(item && item.score);
if (isNaN(score)) return '';
if (score > 85) return 'score-high';
if (score < 20) return 'score-low';
return 'score-mid';
},
enrichRelevanceAnnotations(item) {
if (!item || (Array.isArray(item.annotations) && item.annotations.length)) return;
const needFix = this.isRelevanceNeedModify(item);
@@ -1102,101 +1224,13 @@ export default {
section: 'Introduction',
section_label: '引言',
cite_snippet:
'…the global cancer burden has increased substantially in recent decades [1], highlighting the need for updated estimates…',
score: 98,
'…deep learning has been widely applied to medical image analysis [1], yet comparative studies between CNN and ViT remain limited…',
score: 91,
level: 'high',
label: '高相关',
need_modify: false,
modify_brief: '引用恰当',
ai_desc: '此处用于支撑「全球癌症负担上升」的核心论点,与文献结论直接对应,引用恰当。'
}
],
[
{
cite_index: 1,
cite_total: 2,
section: 'Methods',
section_label: '方法',
cite_snippet:
'…incidence and mortality data were extracted from population-based cancer registries [2], following GLOBOCAN methodology…',
score: 81,
level: 'high',
label: '较相关',
need_modify: false,
modify_brief: '可保留',
ai_desc: '方法段引用:登记数据来源与统计框架一致,适合作为方法学依据。'
},
{
cite_index: 2,
cite_total: 2,
section: 'Discussion',
section_label: '讨论',
cite_snippet:
'…compared with previous reports [2], our estimates for lung cancer showed a divergent trend in females…',
score: 58,
level: 'medium',
label: '部分相关',
need_modify: true,
modify_brief: '建议精简或改写',
ai_desc: '讨论段引用:仅用于横向对比单一癌种趋势,与本文多癌种综合结果关联较弱,宜精简表述。'
}
],
[
{
cite_index: 1,
cite_total: 1,
section: 'Introduction',
section_label: '引言末段',
cite_snippet: '…epidemiological transitions in low- and middle-income countries remain understudied [3]…',
score: 72,
level: 'medium',
label: '背景相关',
need_modify: true,
modify_brief: '不宜作主要依据',
ai_desc: '仅作区域背景铺垫,不宜作为全文主要结论来源。'
}
],
[],
[
{
cite_index: 1,
cite_total: 3,
section: 'Results',
section_label: '结果',
cite_snippet:
'…the age-standardized incidence rate (ASIR) was 某值 per 100,000 [4], consistent with prior national reports…',
score: 88,
level: 'high',
label: '高相关',
need_modify: false,
modify_brief: '可保留',
ai_desc: '结果段第 1 处ASIR 数据口径与文献一致,可直接对照。'
},
{
cite_index: 2,
cite_total: 3,
section: 'Methods',
section_label: '方法',
cite_snippet: '…Bayesian hierarchical models were applied as described previously [4]…',
score: 76,
level: 'medium',
label: '方法参考',
need_modify: true,
modify_brief: '需补充差异说明',
ai_desc: '方法段第 2 处:借用建模思路,但研究人群与年份范围不同,需说明差异。'
},
{
cite_index: 3,
cite_total: 3,
section: 'Discussion',
section_label: '讨论',
cite_snippet: '…future studies should integrate screening programs [4] to reduce mortality…',
score: 42,
level: 'low',
label: '弱相关',
need_modify: true,
modify_brief: '建议删除',
ai_desc: '讨论段第 3 处:文献侧重治疗与筛查路径,与本文流行病学估计主题偏离,建议弱化或删除。'
ai_desc: '引言段用于概括深度学习在医学影像中的应用背景,与文献主题一致,引用恰当。'
}
],
[
@@ -1205,21 +1239,222 @@ export default {
cite_total: 1,
section: 'Introduction',
section_label: '引言',
cite_snippet: '…as reported in 2023 [6], whereas the reference publication year is 2024…',
score: 65,
cite_snippet:
'…recent work compared CNN and Vision Transformers for COVID-19 detection on chest imaging [2], reporting competitive performance…',
score: 96,
level: 'high',
label: '高相关',
need_modify: false,
modify_brief: '可保留',
ai_desc: '直接支撑 CNN 与 ViT 在 COVID-19 影像检测中的对比论述,高度相关。'
}
],
[
{
cite_index: 1,
cite_total: 2,
section: 'Methods',
section_label: '方法',
cite_snippet:
'…we adopted a transfer-learning pipeline similar to prior brain MRI studies [3], including data augmentation and five-fold cross-validation…',
score: 84,
level: 'high',
label: '较相关',
need_modify: false,
modify_brief: '可保留',
ai_desc: '方法段引用:实验流程与文献中的 CNN/ViT 对比设置基本一致,可作为方法参考。'
},
{
cite_index: 2,
cite_total: 2,
section: 'Discussion',
section_label: '讨论',
cite_snippet:
'…unlike [3], our dataset focused on multi-class tumor grading rather than binary classification…',
score: 55,
level: 'medium',
label: '部分相关',
need_modify: true,
modify_brief: '建议精简或改写',
ai_desc: '讨论段仅用于对比任务设定差异,与本文核心结论关联偏弱,建议精简表述。'
}
],
[
{
cite_index: 1,
cite_total: 1,
section: 'Introduction',
section_label: '引言',
cite_snippet:
'…as reported in 2023 [4], whereas the cited work was published in 2024…',
score: 63,
level: 'medium',
label: '待核实',
need_modify: true,
modify_brief: '年份不一致',
ai_desc: '正文年份与文献出版年不一致,需核对后再保留该处引用。'
}
],
[
{
cite_index: 1,
cite_total: 3,
section: 'Results',
section_label: '结果',
cite_snippet:
'…ViT achieved higher accuracy than CNN on the brain MRI dataset [4], consistent with previous comparative analyses…',
score: 93,
level: 'high',
label: '高相关',
need_modify: false,
modify_brief: '可保留',
ai_desc: '结果段第 1 处:直接引用 CNN 与 ViT 在 Brain MRI 数据集上的性能对比结论,高度匹配。'
},
{
cite_index: 2,
cite_total: 3,
section: 'Methods',
section_label: '方法',
cite_snippet: '…model training followed the protocol described in [4], with batch size 32 and Adam optimizer…',
score: 78,
level: 'medium',
label: '方法参考',
need_modify: true,
modify_brief: '需补充差异说明',
ai_desc: '方法段借用训练超参数设置,但数据集规模与预处理步骤不同,需补充说明。'
},
{
cite_index: 3,
cite_total: 3,
section: 'Discussion',
section_label: '讨论',
cite_snippet: '…future work may explore lightweight ViT variants for edge deployment [4]…',
score: 38,
level: 'low',
label: '弱相关',
need_modify: true,
modify_brief: '建议删除',
ai_desc: '讨论段第 3 处:文献未涉及边缘部署主题,与本文实验结论关联较弱,建议弱化或删除。'
}
],
[
{
cite_index: 1,
cite_total: 1,
section: 'Introduction',
section_label: '引言',
cite_snippet:
'…hybrid CNN-Transformer architectures have also been proposed for radiological diagnosis [5]…',
score: 68,
level: 'medium',
label: '背景相关',
need_modify: true,
modify_brief: '不宜作主要依据',
ai_desc: '仅作混合架构背景铺垫,不宜作为本文 CNN vs ViT 对比的主要论据。'
}
],
[
{
cite_index: 1,
cite_total: 1,
section: 'Results',
section_label: '结果',
cite_snippet:
'…as shown in [6], ViT outperformed CNN when sufficient training data were available…',
score: 90,
level: 'high',
label: '高相关',
need_modify: false,
modify_brief: '可保留',
ai_desc: '与重复条目 [4] 相同文献,此处引用结论与正文数据量依赖性论述一致。'
}
],
[
{
cite_index: 1,
cite_total: 1,
section: 'Discussion',
section_label: '讨论',
cite_snippet:
'…attention mechanisms in ViT may improve interpretability over conventional CNNs [7]…',
score: 82,
level: 'high',
label: '较相关',
need_modify: false,
modify_brief: '可保留',
ai_desc: '讨论段引用 ViT 可解释性优势,与本文模型对比讨论方向一致。'
}
],
[
{
cite_index: 1,
cite_total: 2,
section: 'Methods',
section_label: '方法',
cite_snippet: '…preprocessing included skull stripping and intensity normalization as in [8]…',
score: 74,
level: 'medium',
label: '部分相关',
need_modify: true,
modify_brief: '建议精简或改写',
ai_desc: '预处理流程引用合理,但文献针对 CT 影像,与本文 MRI 模态不完全匹配,宜注明差异。'
},
{
cite_index: 2,
cite_total: 2,
section: 'Introduction',
section_label: '引言',
cite_snippet: '…automated screening systems have reduced diagnostic workload [8]…',
score: 48,
level: 'low',
label: '弱相关',
need_modify: true,
modify_brief: '建议删除',
ai_desc: '筛查系统论述与本文模型性能对比主题偏离,建议删除或改述。'
}
],
[
{
cite_index: 1,
cite_total: 1,
section: 'Discussion',
section_label: '讨论',
cite_snippet:
'…ensemble methods combining CNN and ViT predictions may further improve robustness [9]…',
score: 87,
level: 'high',
label: '高相关',
need_modify: false,
modify_brief: '引用恰当',
ai_desc: '讨论段展望集成策略,与全文 CNN/ViT 对比研究脉络一致,可保留。'
}
],
[
{
cite_index: 1,
cite_total: 1,
section: 'Conclusion',
section_label: '结论',
cite_snippet:
'…our findings align with prior surveys on transformer-based medical imaging [10]…',
score: 94,
level: 'high',
label: '高相关',
need_modify: false,
modify_brief: '可保留',
ai_desc: '结论段综述性引用,用于归纳 transformer 在医学影像中的研究趋势,匹配度高。'
}
]
];
return pools[index % pools.length] || [];
return pools[index] || pools[index % pools.length] || [];
},
getRefRelevanceList(row) {
if (!row || !Array.isArray(row.ai_relevance_list)) return [];
const list = row.ai_relevance_list.filter((item) => item && (item.ai_desc || item.score != null));
let list = row.ai_relevance_list.filter((item) => item && (item.ai_desc || item.score != null));
const visible = row._refAiVisibleCount;
if (row._refAiState === 'done' && visible != null && visible > 0 && visible < list.length) {
list = list.slice(0, visible);
}
return list.slice().sort((a, b) => {
const ia = Number(a.cite_index);
const ib = Number(b.cite_index);
@@ -3449,6 +3684,23 @@ export default {
flex-wrap: wrap;
gap: 0;
}
.ref-table-toolbar-right {
flex-shrink: 0;
margin-left: auto;
}
.ref-ai-cell--loading {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
min-height: 48px;
color: #606266;
font-size: 12px;
}
.ref-ai-loading-icon {
font-size: 16px;
color: #409eff;
}
.ref-ai-filter-group {
flex-shrink: 0;
}
@@ -3694,6 +3946,18 @@ export default {
color: #606266;
flex-shrink: 0;
}
.ref-ai-item-score.score-high {
color: #389e0d;
font-weight: 600;
}
.ref-ai-item-score.score-mid {
color: #d48806;
font-weight: 600;
}
.ref-ai-item-score.score-low {
color: #cf1322;
font-weight: 600;
}
.ref-ai-item-verdict {
margin: 0;
font-weight: 600;