添加期刊引用分析

This commit is contained in:
2026-06-17 09:35:15 +08:00
parent ec4a59eedb
commit ea5695f913
36 changed files with 6129 additions and 103 deletions

View File

@@ -165,6 +165,22 @@ const en = {
databases:'Database inclusion',
},
journalCiteMate: {
button: 'Citation analysis',
yearRange: 'Time range',
yearOptionOne: 'Last {n} year',
yearOptionOther: 'Last {n} years',
yearPlaceholder: 'Enter years',
yearInvalid: 'Please enter a whole number greater than 0.',
yearMax: 'Time range cannot exceed 100 years.',
refresh: 'Refresh',
topicTitle: 'Topics',
countryTitle: 'country',
name: 'Name',
citeCount: 'Citations',
totalCites: 'Total citations',
loadFail: 'Failed to load citation analysis',
},
partyRole: {
identity: 'Identity',
@@ -939,6 +955,8 @@ const en = {
exitSourceMode: 'Exit source mode',
sourcePlaceholder: 'Paste or edit full HTML here (DOCTYPE, html, head, body supported)',
editorPlaceholder: 'Please enter...',
clearEditor: 'Clear',
clearEditorConfirm: 'Clear all email content?',
validateTo: 'Please add at least one addressee',
validateSubject: 'Please enter mail subject',
sendSuccess: 'Send success',
@@ -1128,6 +1146,54 @@ const en = {
AnnotationList: 'Annotation List',
Annotations: 'Comments',
exportWord: 'Export Word',
exportManuscriptEmpty: 'No content to export.',
exportManuscriptSuccess: 'Word downloaded.',
exportManuscriptFail: 'Failed to export Word.',
citeRelevanceDetect: 'Download relevance HTML',
citeRelevanceTitle: 'Citation relevance check',
citeRelevanceTitleProgress: 'Citation relevance ({current}/{total})',
citeRelevanceEmpty: 'No citations to review.',
citeRelevanceNoContent: 'No manuscript content.',
citeRelevanceNoCitations: 'No citation markers found (e.g. [1], [1-7]).',
citeRelevanceLoadFail: 'Failed to load references.',
citeRelevanceParagraph: 'Paragraph {n}',
citeRelevanceRefLabel: 'Reference [{n}]',
citeRelevanceRefMissing: 'Reference not found',
citeRelevanceParagraphTitle: 'Paragraph',
citeRelevanceReferenceTitle: 'Reference [{n}]',
citeRelevanceWenaiTitle: 'Full prompt for WenAI',
citeRelevanceCopy: 'Copy',
citeRelevanceCopyWenai: 'Copy full prompt',
citeRelevanceCopySuccess: 'Copied to clipboard',
citeRelevanceCopyFail: 'Copy failed',
citeRelevanceCopyEmpty: 'Nothing to copy',
citeRelevancePrev: 'Previous',
citeRelevanceNext: 'Next',
citeRelevanceClose: 'Close',
citeRelevanceCopyAllWenai: 'Copy all prompts',
citeRelevanceBatchTip: '{n} references per batch within each paragraph',
citeRelevanceRefBatch: 'Batch refs {nums}',
citeRelevanceBatchProgress: 'Paragraph batch {current}/{total}',
citeRelevanceGroupTotal: '{n} paragraphs to review',
citeRelevanceGroupNavLabel: 'Para {paragraph} · {refs}',
citeRelevanceJumpTo: 'Jump to',
citeRelevanceCopyGroup: 'Copy paragraph',
citeRelevanceDownloadHtml: 'Download HTML',
citeRelevanceDownloadHtmlSuccess: 'HTML downloaded',
refHtmlDownload: 'Download references HTML',
refHtmlDownloadSuccess: 'References HTML downloaded',
refHtmlToWord: 'References HTML to Word',
refHtmlToWordSuccess: 'Word exported with edited references',
refHtmlToWordFail: 'Failed to convert references HTML to Word',
refHtmlLoadFail: 'Failed to load references',
refHtmlEmpty: 'No references found',
refHtmlParseEmpty: 'No reference entries found in HTML. Please use the HTML downloaded from this system.',
refHtmlPageTitle: 'References editor',
refHtmlReferencesTitle: 'References',
refHtmlIntro: 'Edit each reference directly, or paste AI output (supports <i> italics and other HTML). When done, click "Download edited HTML", then use "References HTML to Word" on the typesetting page.',
refHtmlSaveTip: 'Tip: save the HTML after editing, then import it on the typesetting page to export Word.',
refHtmlDownloadEdited: 'Download edited HTML',
refHtmlDownloadFileName: 'references-edited.html',
exportImg: 'Export PNG',
PaperRotation: 'Paper Rotation',
removeAnnotations: 'Are you sure you want to delete this Annotation?',

View File

@@ -158,6 +158,22 @@ const zh = {
databases:'收录数据库',
},
journalCiteMate: {
button: '期刊引用分析',
yearRange: '统计范围',
yearOptionOne: '近 {n} 年',
yearOptionOther: '近 {n} 年',
yearPlaceholder: '输入年数',
yearInvalid: '请输入大于 0 的整数',
yearMax: '统计范围最多为 100 年',
refresh: '刷新',
topicTitle: '话题引用',
countryTitle: 'country',
name: '名称',
citeCount: '引用数量',
totalCites: '引用总数',
loadFail: '加载期刊引用分析失败',
},
paperArticleCount: {
Periodroll: '期卷',
article: '文章',
@@ -924,6 +940,8 @@ const zh = {
exitSourceMode: '退出源码编辑',
sourcePlaceholder: '在此粘贴或编辑完整 HTML支持 <!DOCTYPE>、<html>、<head>、<body> 等',
editorPlaceholder: '请输入邮件内容...',
clearEditor: '清空',
clearEditorConfirm: '确定清空邮件正文吗?',
validateTo: '请至少添加一个收件人',
validateSubject: '请输入邮件主题',
sendSuccess: '发送成功',
@@ -1114,6 +1132,54 @@ const zh = {
AnnotationList: '批注列表',
Annotations: '批注',
exportWord: '导出 Word',
exportManuscriptEmpty: '没有可导出的内容。',
exportManuscriptSuccess: 'Word 已下载。',
exportManuscriptFail: 'Word 导出失败。',
citeRelevanceDetect: '下载相关性 HTML',
citeRelevanceTitle: '引文相关性核查',
citeRelevanceTitleProgress: '引文相关性核查 ({current}/{total})',
citeRelevanceEmpty: '当前没有可核查的引文。',
citeRelevanceNoContent: '没有正文内容。',
citeRelevanceNoCitations: '正文中未找到引用标记(如 [1]、[1-7])。',
citeRelevanceLoadFail: '加载参考文献失败。',
citeRelevanceParagraph: '正文段落 {n}',
citeRelevanceRefLabel: '参考文献 [{n}]',
citeRelevanceRefMissing: '未找到对应文献',
citeRelevanceParagraphTitle: '正文段落',
citeRelevanceReferenceTitle: '参考文献 [{n}]',
citeRelevanceWenaiTitle: '复制到 WenAI 的完整提示',
citeRelevanceCopy: '复制',
citeRelevanceCopyWenai: '复制完整提示',
citeRelevanceCopySuccess: '已复制到剪贴板',
citeRelevanceCopyFail: '复制失败',
citeRelevanceCopyEmpty: '没有可复制的内容',
citeRelevancePrev: '上一条',
citeRelevanceNext: '下一条',
citeRelevanceClose: '关闭',
citeRelevanceCopyAllWenai: '复制全部提示',
citeRelevanceBatchTip: '每段正文按 {n} 条参考文献一批提问',
citeRelevanceRefBatch: '本批文献 {nums}',
citeRelevanceBatchProgress: '本段第 {current}/{total} 批',
citeRelevanceGroupTotal: '共 {n} 段待核查',
citeRelevanceGroupNavLabel: '段落 {paragraph} · {refs}',
citeRelevanceJumpTo: '快速跳转',
citeRelevanceCopyGroup: '复制本段',
citeRelevanceDownloadHtml: '下载 HTML',
citeRelevanceDownloadHtmlSuccess: 'HTML 已下载',
refHtmlDownload: '下载参考文献 HTML',
refHtmlDownloadSuccess: '参考文献 HTML 已下载',
refHtmlToWord: '参考文献 HTML 转 Word',
refHtmlToWordSuccess: 'Word 已导出(含编辑后的参考文献)',
refHtmlToWordFail: '参考文献 HTML 转 Word 失败',
refHtmlLoadFail: '加载参考文献失败',
refHtmlEmpty: '暂无参考文献',
refHtmlParseEmpty: 'HTML 中未找到参考文献条目,请使用本系统下载的参考文献 HTML',
refHtmlPageTitle: '参考文献编辑',
refHtmlReferencesTitle: 'References',
refHtmlIntro: '可直接编辑每条文献,或粘贴 AI 返回的内容(支持 <i> 斜体等 HTML 标签)。编辑完成后点击「下载编辑后的 HTML」再在排版页使用「参考文献 HTML 转 Word」。',
refHtmlSaveTip: '提示:编辑后请点击上方按钮保存 HTML也可在排版页导入该文件导出 Word。',
refHtmlDownloadEdited: '下载编辑后的 HTML',
refHtmlDownloadFileName: 'references-edited.html',
exportImg: '导出 图片',
PaperRotation: '纸张方向',
removeAnnotations: '确定要删除这条批注吗?',

View File

@@ -92,6 +92,7 @@
<div style="width: 100%; width: calc(100% - 260px); float: right; height: calc(100% - 0px); background-color: #e4e9ed">
<common-word
:articleId="articleId"
:pArticleId="pArticleId"
v-if="htmlContent"
ref="commonWord"
:value="htmlContent"
@@ -464,6 +465,7 @@ export default {
ManuscirptContent: [],
mathFormulasList: [],
articleId: this.$route.query.id,
pArticleId: this.$route.query.p_article_id || '',
isShowComment: false,
urlList: {
executeProofreading: 'api/Proofread/change',

View File

@@ -143,6 +143,21 @@
<i class="el-icon-connection" style="color: #4665b0"></i> {{ $t('menu.ClassificationmanagementInfo') }}
</p>
<p
@click="openJournalCiteMate(scope.row)"
style="
background-color: #fdf6ec;
color: #e6a23c;
cursor: pointer;
padding: 2px 2px;
font-weight: bold;
border-radius: 4px;
margin-bottom: 5px;
"
>
<i class="el-icon-data-analysis" style="color: #e6a23c"></i> {{ $t('journalCiteMate.button') }}
</p>
<p
@click="
openJournalAgreement({
@@ -382,6 +397,91 @@
>
<common-agreement ref="commonAgreementRef" :journal="currentJournal" :urlList="urlList"></common-agreement>
</el-drawer>
<el-drawer
append-to-body
destroy-on-close
custom-class="journal-cite-mate-drawer"
:title="$t('GroupClassification.Journal') + ' : ' + currentJournal.title"
:visible.sync="drawerCiteMate"
direction="rtl"
:before-close="handleCiteMateClose"
:wrapperClosable="false"
size="70%"
@opened="updateCiteMateTableHeight"
>
<div class="journal-cite-mate-panel" v-loading="citeMateLoading">
<div class="journal-cite-mate-toolbar">
<span class="journal-cite-mate-label">{{ $t('journalCiteMate.yearRange') }}</span>
<el-select
v-model="citeMateYear"
size="small"
class="journal-cite-mate-year-select"
filterable
allow-create
default-first-option
:placeholder="$t('journalCiteMate.yearPlaceholder')"
@change="handleCiteMateYearChange"
>
<el-option
v-for="item in citeMateYearOptionList"
:key="item"
:label="formatCiteMateYearLabel(item)"
:value="String(item)"
></el-option>
</el-select>
<el-button size="small" type="primary" plain icon="el-icon-refresh" @click="loadJournalCiteMate">
{{ $t('journalCiteMate.refresh') }}
</el-button>
<span v-if="citeMateCitesNum != null" class="journal-cite-mate-total">
{{ $t('journalCiteMate.totalCites') }}<strong>{{ citeMateCitesNum }}</strong>
</span>
</div>
<el-row :gutter="16" class="journal-cite-mate-body">
<el-col :span="12">
<div ref="citeMateBlock" class="journal-cite-mate-block">
<div class="journal-cite-mate-block-title">
<span class="journal-cite-mate-block-name">{{ $t('journalCiteMate.topicTitle') }}</span>
<span class="journal-cite-mate-count">{{ citeMateTopicList.length }}</span>
</div>
<el-table :data="citeMateTopicList" border size="mini" :height="citeMateTableHeight" empty-text="">
<el-table-column type="index" label="#" width="50" align="center"></el-table-column>
<el-table-column :label="$t('journalCiteMate.name')" min-width="160">
<template slot-scope="scope">
{{ getCiteMateRowName(scope.row) }}
</template>
</el-table-column>
<el-table-column :label="$t('journalCiteMate.citeCount')" width="100" align="center">
<template slot-scope="scope">
{{ getCiteMateRowCount(scope.row) }}
</template>
</el-table-column>
</el-table>
</div>
</el-col>
<el-col :span="12">
<div class="journal-cite-mate-block">
<div class="journal-cite-mate-block-title">
<span class="journal-cite-mate-block-name">{{ $t('journalCiteMate.countryTitle') }}</span>
<span class="journal-cite-mate-count">{{ citeMateCountryList.length }}</span>
</div>
<el-table :data="citeMateCountryList" border size="mini" :height="citeMateTableHeight" empty-text="">
<el-table-column type="index" label="#" width="50" align="center"></el-table-column>
<el-table-column :label="$t('journalCiteMate.name')" min-width="160">
<template slot-scope="scope">
{{ getCiteMateRowName(scope.row) }}
</template>
</el-table-column>
<el-table-column :label="$t('journalCiteMate.citeCount')" width="100" align="center">
<template slot-scope="scope">
{{ getCiteMateRowCount(scope.row) }}
</template>
</el-table-column>
</el-table>
</div>
</el-col>
</el-row>
</div>
</el-drawer>
<el-dialog :title="$t('JournalCitationAnalysis.feeDialogTitle')" :visible.sync="feeDialogVisible" width="800px">
<el-alert
:title="$t('JournalCitationAnalysis.feeWarningTitle')"
@@ -442,6 +542,23 @@ import commonclass from './class.vue';
import commonAgreement from './agreement.vue';
import commonJournalInstallment from '../JournalInstallment/index.vue';
import bus from '@/components/common/bus';
function normalizeCiteMateLists(data) {
if (!data) {
return { topics: [], countries: [], citesNum: null };
}
if (Array.isArray(data)) {
return { topics: data, countries: [], citesNum: null };
}
const topics = data.topic || data.topics || data.topic_list || data.topicList || [];
const countries = data.country || data.countries || data.country_list || data.countryList || data.nation || data.nation_list || [];
const citesNum = data.cites_num != null ? data.cites_num : data.cite_num != null ? data.cite_num : data.total != null ? data.total : null;
return {
topics: Array.isArray(topics) ? topics : [],
countries: Array.isArray(countries) ? countries : [],
citesNum: citesNum
};
}
export default {
name: 'app',
props: ['urlList', 'source'],
@@ -500,6 +617,14 @@ export default {
drawer: false,
drawerJournalInstallment: false,
drawerAgreement: false,
drawerCiteMate: false,
citeMateLoading: false,
citeMateYear: '2',
citeMateYearOptions: [1, 2, 3, 4, 5, 6],
citeMateTopicList: [],
citeMateCountryList: [],
citeMateCitesNum: null,
citeMateTableHeight: 420,
bd: true,
ruleForm: {
major_title: '',
@@ -607,6 +732,28 @@ export default {
this.ruleForm = {};
}
}
},
drawerCiteMate(val) {
if (val) {
this.$nextTick(() => {
this.updateCiteMateTableHeight();
window.addEventListener('resize', this.updateCiteMateTableHeight);
});
} else {
window.removeEventListener('resize', this.updateCiteMateTableHeight);
}
}
},
computed: {
citeMateYearOptionList() {
const presets = this.citeMateYearOptions.slice();
const current = parseInt(this.citeMateYear, 10);
if (Number.isFinite(current) && current > 0 && presets.indexOf(current) === -1) {
presets.push(current);
}
return presets.sort(function (a, b) {
return a - b;
});
}
},
async created() {
@@ -617,6 +764,9 @@ export default {
async activated() {
await this.getDate();
},
beforeDestroy() {
window.removeEventListener('resize', this.updateCiteMateTableHeight);
},
methods: {
// 失去焦点事件
onEditorBlur(quill) {
@@ -960,6 +1110,118 @@ export default {
that.$refs.commonAgreementRef.init();
});
},
openJournalCiteMate(data) {
this.currentJournal = data || {};
this.citeMateYear = '2';
this.drawerCiteMate = true;
this.loadJournalCiteMate();
},
handleCiteMateClose(done) {
this.drawerCiteMate = false;
if (typeof done === 'function') {
done();
}
},
updateCiteMateTableHeight() {
this.$nextTick(() => {
const block = this.$refs.citeMateBlock;
if (!block) {
return;
}
const el = Array.isArray(block) ? block[0] : block;
const title = el.querySelector('.journal-cite-mate-block-title');
const style = window.getComputedStyle(el);
const paddingTop = parseFloat(style.paddingTop) || 0;
const paddingBottom = parseFloat(style.paddingBottom) || 0;
let titleHeight = 0;
if (title) {
const titleStyle = window.getComputedStyle(title);
titleHeight = title.offsetHeight + (parseFloat(titleStyle.marginBottom) || 0);
}
const nextHeight = el.clientHeight - paddingTop - paddingBottom - titleHeight;
if (nextHeight > 0) {
this.citeMateTableHeight = nextHeight;
}
});
},
formatCiteMateYearLabel(years) {
const num = parseInt(years, 10);
if (!Number.isFinite(num) || num < 1) {
return String(years);
}
if (num === 1) {
return this.$t('journalCiteMate.yearOptionOne', { n: num });
}
return this.$t('journalCiteMate.yearOptionOther', { n: num });
},
normalizeCiteMateYear(value) {
const parsed = parseInt(String(value || '').trim(), 10);
if (!Number.isFinite(parsed) || parsed < 1) {
this.$message.warning(this.$t('journalCiteMate.yearInvalid'));
return '2';
}
if (parsed > 100) {
this.$message.warning(this.$t('journalCiteMate.yearMax'));
return '100';
}
return String(parsed);
},
handleCiteMateYearChange(value) {
this.citeMateYear = this.normalizeCiteMateYear(value);
this.loadJournalCiteMate();
},
getCiteMateRowName(row) {
if (!row) {
return '—';
}
return row.topic_name || row.country || row.country_name || row.nation_name || row.name || row.title || '—';
},
getCiteMateRowCount(row) {
const value = row.num != null ? row.num : row.count != null ? row.count : row.cite_num;
return value != null ? value : '—';
},
loadJournalCiteMate() {
const journalId = this.currentJournal && this.currentJournal.journal_id;
if (!journalId) {
this.citeMateTopicList = [];
this.citeMateCountryList = [];
this.citeMateCitesNum = null;
return;
}
this.citeMateLoading = true;
this.$api
.post('api/journal/citeMate', {
journal_id: String(journalId),
year: String(this.citeMateYear || '2')
})
.then((res) => {
if (res.code == 0) {
const lists = normalizeCiteMateLists(res.data);
this.citeMateTopicList = lists.topics;
this.citeMateCountryList = lists.countries;
this.citeMateCitesNum = lists.citesNum;
} else {
this.citeMateTopicList = [];
this.citeMateCountryList = [];
this.citeMateCitesNum = null;
this.$message.error(res.msg || this.$t('journalCiteMate.loadFail'));
}
})
.catch((err) => {
console.error(err);
this.citeMateTopicList = [];
this.citeMateCountryList = [];
this.citeMateCitesNum = null;
this.$message.error(this.$t('journalCiteMate.loadFail'));
})
.finally(() => {
this.citeMateLoading = false;
if (this.drawerCiteMate) {
this.updateCiteMateTableHeight();
}
});
},
renameKeys(obj, oldKey, newKey) {
if (obj[oldKey]) {
obj[newKey] = obj[oldKey];
@@ -1457,6 +1719,100 @@ export default {
padding: 22px 0 0 0;
text-align: center;
}
.journal-cite-mate-panel {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
padding: 0 20px 20px;
box-sizing: border-box;
}
.journal-cite-mate-drawer .el-drawer__body {
display: flex;
flex-direction: column;
overflow: hidden;
padding-bottom: 0;
}
.journal-cite-mate-body {
flex: 1;
min-height: 0;
}
.journal-cite-mate-body.el-row {
display: flex;
flex-wrap: nowrap;
height: 100%;
}
.journal-cite-mate-body > .el-col {
display: flex;
flex-direction: column;
height: 100%;
}
.journal-cite-mate-block {
display: flex;
flex-direction: column;
height: 100%;
min-height: 0;
box-sizing: border-box;
background: #fff;
border: 1px solid #ebeef5;
border-radius: 4px;
padding: 12px;
}
.journal-cite-mate-toolbar {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 16px;
flex-shrink: 0;
}
.journal-cite-mate-year-select {
width: 150px;
}
.journal-cite-mate-label {
font-size: 13px;
color: #606266;
}
.journal-cite-mate-block-title {
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
}
.journal-cite-mate-block-name {
font-size: 14px;
font-weight: 600;
color: #303133;
}
.journal-cite-mate-count {
font-weight: 600;
color: #409eff;
font-size: 16px;
line-height: 1;
}
.journal-cite-mate-total {
margin-left: auto;
font-size: 14px;
color: #303133;
}
.journal-cite-mate-total strong {
color: #409eff;
font-size: 16px;
}
.WeChatCode .avatar-uploader .el-upload {
width: 120px !important;
height: 120px !important;

View File

@@ -1421,7 +1421,8 @@ export default {
this.$router.resolve({
path: '/GenerateCharts',
query: {
id: this.$route.query.id
id: this.$route.query.id,
p_article_id: this.p_article_id
}
}).href,
'_blank'

View File

@@ -54,7 +54,21 @@
<!-- 基本信息 -->
<div :ref="tabsList[0].refName" class="scroll-item">
<div class="bor_style_onli" style="margin: 20px 0">
<h4>{{ tabsList[0].name }}</h4>
<h4>
{{ tabsList[0].name }}
<el-button
v-if="zyModeEnabled"
style="float: right"
type="warning"
plain
icon="el-icon-download"
:loading="importingSubmission"
:disabled="!detailMes.article_id"
@click="importFromSubmission"
>
Import from Submission
</el-button>
</h4>
<el-form ref="Mes_Form" :model="detailMes" :rules="rules" label-width="220px">
<el-form-item label="Title :" prop="title">
<el-input v-model="detailMes.title"></el-input>
@@ -235,7 +249,14 @@
Add Author</el-button
>
</h4>
<el-table :data="authorData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
<el-table
:key="'author-' + authorTableKey"
:data="authorData"
border
class="table"
ref="authorTable"
header-cell-class-name="table-header"
>
<el-table-column prop="author_name" label="Author"></el-table-column>
<el-table-column label="First author" align="center" width="100px">
<template slot-scope="scope">
@@ -454,7 +475,14 @@
>
</h4>
<!-- 机构列表 -->
<el-table :data="schoolData" border class="table" ref="multipleTable" header-cell-class-name="table-header">
<el-table
:key="'school-' + schoolTableKey"
:data="schoolData"
border
class="table"
ref="schoolTable"
header-cell-class-name="table-header"
>
<el-table-column type="index" label="No." width="55" align="center"></el-table-column>
<el-table-column prop="organ_name" label="Affiliation"></el-table-column>
<el-table-column label="" width="190" align="center">
@@ -952,6 +980,79 @@
</div>
</div>
<!-- 导入前作者预览 -->
<el-dialog
title="Review Authors Before Import"
:visible.sync="importAuthorPreviewVisible"
width="960px"
:close-on-click-modal="false"
>
<p style="color: #666; line-height: 22px; margin: 0 0 12px 0">
Check recognized authors, adjust order manually, or paste a name sequence to auto-sort. Required empty fields will be saved as a space.
</p>
<el-input
v-model="importAuthorOrderText"
type="textarea"
:autosize="{ minRows: 2, maxRows: 4 }"
placeholder="eg: Sijing PENG1,¶, Ting ZHENG1,¶, Jin CONG1, Xiang WANG1, Chuanying ZHANG1, Jiejie GE1,2*"
></el-input>
<div style="margin: 10px 0 16px 0">
<el-button size="mini" type="primary" plain @click="applyImportAuthorOrder">Apply Order</el-button>
</div>
<el-table :data="importPreviewAuthors" border size="small" max-height="360">
<el-table-column type="index" label="No." width="50" align="center"></el-table-column>
<el-table-column label="First name" min-width="120">
<template slot-scope="scope">
{{ scope.row.firstname || scope.row.first_name }}
</template>
</el-table-column>
<el-table-column label="Last name" min-width="100">
<template slot-scope="scope">
{{ scope.row.lastname || scope.row.last_name }}
</template>
</el-table-column>
<el-table-column label="First author" width="100" align="center">
<template slot-scope="scope">
<el-tag round :type="Number(scope.row.is_super) === 1 ? 'success' : 'danger'" size="mini">
{{ Number(scope.row.is_super) === 1 ? '√' : '×' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="Corresponding" width="110" align="center">
<template slot-scope="scope">
<el-tag round :type="Number(scope.row.is_report) === 1 ? 'success' : 'danger'" size="mini">
{{ Number(scope.row.is_report) === 1 ? '√' : '×' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="email" label="Email" min-width="150" show-overflow-tooltip>
<template slot-scope="scope">
{{ Number(scope.row.is_report) === 1 ? scope.row.email || ' ' : '' }}
</template>
</el-table-column>
<el-table-column label="Affiliation" min-width="220" show-overflow-tooltip>
<template slot-scope="scope">
{{ getImportAuthorAffiliation(scope.row) }}
</template>
</el-table-column>
<el-table-column label="Order" width="100" align="center">
<template slot-scope="scope">
<el-button type="text" :disabled="scope.$index === 0" @click="moveImportAuthor(scope.$index, 'up')">Up</el-button>
<el-button
type="text"
:disabled="scope.$index === importPreviewAuthors.length - 1"
@click="moveImportAuthor(scope.$index, 'down')"
>Down</el-button
>
</template>
</el-table-column>
</el-table>
<span slot="footer" class="dialog-footer">
<el-button @click="importAuthorPreviewVisible = false">Cancel</el-button>
<el-button type="primary" :loading="importingSubmission" @click="confirmImportFromSubmission">Confirm Import</el-button>
</span>
</el-dialog>
<!-- 上线弹出框 --->
<el-dialog title="Tips" :visible.sync="onlineVisible" width="680px" :close-on-click-modal="false">
<p style="line-height: 24px">Are you sure you want to push this manuscript to the official website? You can preview first.</p>
@@ -1067,9 +1168,31 @@ import comArtHtmlCreatNewProduce from './comArtHtmlCreatNewProduce.vue';
import commonTopic from '../page/components/article/topic.vue';
import commonRelated from '../page/components/article/Related.vue';
import Tinymce from '@/components/page/components/Tinymce';
import {
mapEssentialFields,
buildOrganIdMap,
buildProductionAuthorPayload,
buildAuthorReferenceAbbr,
collectSubmissionOrgans,
resolveSubmissionAuthors,
normalizeSubmissionAuthors,
normalizeRichTextContent,
sortAuthorsByOrderString,
moveAuthorInList,
getAuthorDisplayName,
getAuthorAffiliationPreview
} from '@/utils/productionSubmissionImport';
import { isZyModeEnabled } from '@/utils/zyMode';
export default {
data() {
return {
importingSubmission: false,
importAuthorPreviewVisible: false,
importAuthorOrderText: '',
importPreviewAuthors: [],
importPreviewContext: null,
authorTableKey: 0,
schoolTableKey: 0,
isGenerating: false, // 按钮加载状态
percentage: 0, // 进度条
finalReview: null,
@@ -1609,6 +1732,19 @@ export default {
updateChange(type, content) {
this[type] = content;
},
applyRichTextToEditor(refName, field, content) {
const normalized = normalizeRichTextContent(content);
this[field] = normalized;
if (field === 'abstract') {
this.detailMes.abstract = normalized;
}
this.$nextTick(() => {
const editor = this.$refs[refName];
if (editor && typeof editor.setContent === 'function') {
editor.setContent(normalized);
}
});
},
getTinymceContent(type) {
this.$refs.tinymceChild1.getContent(type);
@@ -1905,34 +2041,278 @@ export default {
},
getAuthorJG() {
// 获取作者列表
this.$api
.post('api/Production/getAuthorlist', this.idform)
.then((res) => {
if (res.code == 0) {
this.authorData = res.data.authors;
} else {
this.$message.error(res.msg);
}
})
.catch((err) => {
this.$message.error(err);
});
return this.refreshAuthorOrganLists().catch((err) => {
this.$message.error(err);
});
},
refreshAuthorOrganLists() {
this.idform.p_article_id = this.p_article_id;
return Promise.all([
this.$api.post('api/Production/getAuthorlist', this.idform),
this.$api.post('api/Production/getOrganList', this.idform)
]).then(([authorRes, organRes]) => {
if (authorRes.code == 0) {
this.$set(this, 'authorData', [...(authorRes.data.authors || [])]);
} else {
throw authorRes.msg || 'Failed to load authors';
}
if (organRes.code == 0) {
this.$set(this, 'schoolData', [...(organRes.data.organs || [])]);
this.$set(this, 'mechanism', [...(organRes.data.organs || [])]);
} else {
throw organRes.msg || 'Failed to load affiliations';
}
});
},
fetchAuthorList() {
this.idform.p_article_id = this.p_article_id;
return this.$api.post('api/Production/getAuthorlist', this.idform).then((authorRes) => {
if (authorRes.code == 0) {
this.$set(this, 'authorData', [...(authorRes.data.authors || [])]);
return authorRes;
}
throw authorRes.msg || 'Failed to load authors';
});
},
fetchOrganList() {
this.idform.p_article_id = this.p_article_id;
return this.$api.post('api/Production/getOrganList', this.idform).then((organRes) => {
if (organRes.code == 0) {
this.$set(this, 'schoolData', [...(organRes.data.organs || [])]);
this.$set(this, 'mechanism', [...(organRes.data.organs || [])]);
return organRes;
}
throw organRes.msg || 'Failed to load affiliations';
});
},
async refreshAuthorSection(options = {}) {
const { authorsOnly = false, organsOnly = false } = options;
if (organsOnly) {
await this.fetchOrganList();
this.schoolTableKey += 1;
} else if (authorsOnly) {
await this.fetchAuthorList();
this.authorTableKey += 1;
} else {
await this.refreshAuthorOrganLists();
this.authorTableKey += 1;
this.schoolTableKey += 1;
}
await this.$nextTick();
this.$forceUpdate();
await this.$nextTick();
if (this.$refs.authorTable && this.$refs.authorTable.doLayout) {
this.$refs.authorTable.doLayout();
}
if (this.$refs.schoolTable && this.$refs.schoolTable.doLayout) {
this.$refs.schoolTable.doLayout();
}
},
getImportAuthorName(author) {
return getAuthorDisplayName(author);
},
getImportAuthorAffiliation(author) {
return getAuthorAffiliationPreview(author);
},
applyImportAuthorOrder() {
if (!this.importAuthorOrderText.trim()) {
this.$message.warning('Please paste an author order string first.');
return;
}
this.importPreviewAuthors = sortAuthorsByOrderString(this.importPreviewAuthors, this.importAuthorOrderText);
this.$message.success('Author order updated.');
},
moveImportAuthor(index, direction) {
this.importPreviewAuthors = moveAuthorInList(this.importPreviewAuthors, index, direction);
},
async fetchSubmissionImportContext(articleId) {
const detailRes = await this.$api.post('api/Article/getArticleDetail', {
articleId,
human: 'editor'
});
const article = (detailRes && detailRes.article) || {};
const userId = article.user_id;
// 获取机构列表
this.$api
.post('api/Production/getOrganList', this.idform)
.then((res) => {
if (res.code == 0) {
this.schoolData = res.data.organs;
this.mechanism = res.data.organs;
} else {
this.$message.error(res.msg);
const requestList = [this.$api.post('api/Article/getAuthors', { article_id: articleId }).catch(() => null)];
if (userId) {
requestList.push(
this.$api.post('api/Organ/lists', { article_id: articleId, user_id: userId }).catch(() => null)
);
}
const [authorsRes, organsRes] = await Promise.all(requestList);
const authors = normalizeSubmissionAuthors(resolveSubmissionAuthors(authorsRes, detailRes));
const submissionOrgans = collectSubmissionOrgans(
organsRes && Number(organsRes.status) === 1 && Array.isArray(organsRes.data) ? organsRes.data : [],
authors
);
const mapped = mapEssentialFields(article, authors);
return {
article,
authors,
submissionOrgans,
mapped
};
},
applyImportedEssentialFields(mapped) {
this.detailMes.title = mapped.title;
this.detailMes.type = mapped.type;
this.detailMes.keywords = mapped.keywords;
this.detailMes.acknowledgment = mapped.acknowledgment;
this.detailMes.author_contribution = mapped.author_contribution;
this.detailMes.abbreviation = mapped.abbreviation;
this.applyRichTextToEditor('tinymceChild1', 'abstract', mapped.abstract);
},
async importFromSubmission() {
const articleId = this.detailMes && this.detailMes.article_id;
if (!articleId) {
this.$message.error('No linked submission article found.');
return;
}
const loading = this.$loading({
lock: true,
text: 'Loading submission data...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.importingSubmission = true;
try {
const context = await this.fetchSubmissionImportContext(articleId);
this.importPreviewContext = context;
this.importPreviewAuthors = [...context.authors];
this.importAuthorOrderText = '';
this.importAuthorPreviewVisible = true;
} catch (err) {
console.error(err);
this.$message.error(typeof err === 'string' ? err : 'Failed to load submission data.');
} finally {
loading.close();
this.importingSubmission = false;
}
},
async confirmImportFromSubmission() {
if (!this.importPreviewContext) {
return;
}
const loading = this.$loading({
lock: true,
text: 'Importing from submission...',
spinner: 'el-icon-loading',
background: 'rgba(0, 0, 0, 0.7)'
});
this.importingSubmission = true;
try {
const { submissionOrgans, mapped } = this.importPreviewContext;
const authors = [...this.importPreviewAuthors];
this.applyImportedEssentialFields(mapped);
let organIdMap = {};
let authorImported = false;
let affiliationImported = false;
let addedAuthorCount = 0;
const importedAuthors = [];
if (!this.schoolData.length && submissionOrgans.length) {
for (const organ of submissionOrgans) {
const organName = String(organ.organ_name || '').trim();
if (!organName) {
continue;
}
const addRes = await this.$api.post('api/Production/addAuthorOrgan', {
p_article_id: this.p_article_id,
organ_name: organName
});
if (addRes.code != 0) {
throw addRes.msg || 'Failed to add affiliation';
}
}
})
.catch((err) => {
this.$message.error(err);
});
affiliationImported = true;
await this.refreshAuthorSection({ organsOnly: true });
}
organIdMap = buildOrganIdMap(submissionOrgans, this.schoolData);
if (!this.authorData.length && authors.length) {
if (!this.schoolData.length) {
throw 'Please import affiliations before authors.';
}
for (const author of authors) {
const payload = buildProductionAuthorPayload(
author,
this.p_article_id,
organIdMap,
this.schoolData
);
if (!payload.first_name || !payload.last_name) {
continue;
}
if (!payload.organs.length) {
throw `Author ${payload.first_name} ${payload.last_name} has no matched affiliation.`;
}
const addRes = await this.$api.post('api/Production/addAuthor', payload);
if (addRes.code != 0) {
throw addRes.msg || `Failed to add author ${payload.first_name} ${payload.last_name}`;
}
addedAuthorCount += 1;
importedAuthors.push({
...author,
firstname: payload.first_name,
first_name: payload.first_name,
lastname: payload.last_name,
last_name: payload.last_name
});
}
if (addedAuthorCount > 0) {
authorImported = true;
this.detailMes.abbr = buildAuthorReferenceAbbr(importedAuthors);
await this.refreshAuthorSection({ authorsOnly: true });
}
}
if (affiliationImported && !authorImported) {
await this.refreshAuthorSection({ organsOnly: true });
}
const messages = ['Essential Information filled from submission.'];
if (affiliationImported) {
messages.push('Affiliations imported.');
} else if (submissionOrgans.length && this.schoolData.length) {
messages.push('Affiliations already exist, skipped.');
}
if (authorImported) {
messages.push(`${addedAuthorCount} author(s) imported.`);
} else if (authors.length && this.authorData.length) {
messages.push('Authors already exist, skipped.');
} else if (authors.length && !addedAuthorCount) {
messages.push('No authors were imported. Please check author data.');
}
messages.push('Please review the fields and click Save Essential Information.');
this.$message.success(messages.join(' '));
this.importAuthorPreviewVisible = false;
this.importPreviewContext = null;
if (authorImported || affiliationImported) {
await this.$nextTick();
this.jumpTab(1, this.tabsList[1]);
} else {
await this.$nextTick();
this.$forceUpdate();
this.jumpTab(0, this.tabsList[0]);
}
} catch (err) {
console.error(err);
this.$message.error(typeof err === 'string' ? err : 'Import failed. Please try again.');
} finally {
loading.close();
this.importingSubmission = false;
}
},
// 1----时间初始化
@@ -2125,7 +2505,7 @@ export default {
this.addAuthor = false;
this.$refs.add_Author.resetFields();
this.addFomauthor.organs = [];
this.getAuthorJG();
this.refreshAuthorSection({ authorsOnly: true });
} else {
this.$message.error(res.msg);
}
@@ -2213,7 +2593,7 @@ export default {
if (res.code == 0) {
this.addSchool = false;
this.$refs.add_School.resetFields();
this.getAuthorJG();
this.refreshAuthorSection({ organsOnly: true });
} else {
this.$message.error(res.msg);
}
@@ -3210,6 +3590,11 @@ export default {
// this.jumpTab(0,this.tabsList[0])
}
},
computed: {
zyModeEnabled() {
return isZyModeEnabled(this.$route);
}
},
components: {
quillEditor,
Tinymce,

View File

@@ -204,7 +204,8 @@ export default {
goEdit() {
window.open(this.$router.resolve({ path: '/GenerateCharts',
query: {
id: this.detailMes.article_id
id: this.detailMes.article_id,
p_article_id: this.Art_Id
} }).href, '_blank');
},

View File

@@ -0,0 +1,87 @@
<template>
<span v-if="zyModeEnabled" class="ref-search-links">
<template v-if="links.isBook">
<a
v-if="links.googleLink"
class="ref-search-link"
:href="links.googleLink"
target="_blank"
rel="noopener noreferrer"
@click.stop
>
<i class="el-icon-search"></i>
</a>
</template>
<template v-else>
<a
v-if="links.pubmedLink"
class="ref-search-link"
:href="links.pubmedLink"
target="_blank"
rel="noopener noreferrer"
@click.stop
>
<i class="el-icon-search"></i>
</a>
<a
v-else-if="links.scholarLink"
class="ref-search-link"
:href="links.scholarLink"
target="_blank"
rel="noopener noreferrer"
@click.stop
>
<i class="el-icon-search"></i>
</a>
</template>
</span>
</template>
<script>
import { getReferenceLinksFromRow } from '@/utils/referenceLinks';
import { isZyModeEnabled } from '@/utils/zyMode';
export default {
name: 'ReferenceSearchLinks',
props: {
row: {
type: Object,
required: true
}
},
computed: {
zyModeEnabled() {
return isZyModeEnabled(this.$route);
},
links() {
return getReferenceLinksFromRow(this.row);
}
}
};
</script>
<style scoped>
.ref-search-links {
display: inline-flex;
align-items: center;
gap: 2px;
margin-left: 4px;
vertical-align: middle;
}
.ref-search-link {
display: inline-flex;
align-items: center;
justify-content: center;
width: 14px;
height: 14px;
color: #909399;
font-size: 11px;
line-height: 1;
text-decoration: none;
}
.ref-search-link:hover {
color: #409eff;
}
</style>

View File

@@ -13,6 +13,7 @@ export default {
value: { type: String, default: '' },
id: { type: String, default: () => 'tiny-' + +new Date() },
showSelectTemplateButton: { type: Boolean, default: false },
showEmptyButton: { type: Boolean, default: true },
// 仅在需要的页面开启“安全初始化内容”
useSafeInitContent: { type: Boolean, default: false }
},
@@ -121,6 +122,8 @@ autoInlineStyles(htmlContent) {
initTiny() {
const vueInstance = this;
const hasTemplateBtn = this.showSelectTemplateButton;
const hasEmptyBtn = this.showEmptyButton;
const toolbarSuffix = hasEmptyBtn ? ' | clearButton | fullscreen' : ' | fullscreen';
window.tinymce.init({
selector: `#${this.tinymceId}`,
language: 'zh_CN', // 记得下载语言包,没有就删掉这行
@@ -137,8 +140,11 @@ autoInlineStyles(htmlContent) {
// 按照图片布局精准排序:
toolbar: hasTemplateBtn
? 'undo redo | selectTemplate | code preview | fontselect fontsizeselect | formatselect | bold italic underline strikethrough | forecolor backcolor | bullist numlist | lineheight outdent indent | alignleft aligncenter alignright alignjustify | table link | fullscreen'
: 'undo redo | code preview | fontselect fontsizeselect | formatselect | bold italic underline strikethrough | forecolor backcolor | bullist numlist | lineheight outdent indent | alignleft aligncenter alignright alignjustify | table link | fullscreen',
? 'undo redo | selectTemplate | code preview | fontselect fontsizeselect | formatselect | bold italic underline strikethrough | forecolor backcolor | bullist numlist | lineheight outdent indent | alignleft aligncenter alignright alignjustify | table link' +
toolbarSuffix
: 'undo redo | code preview | fontselect fontsizeselect | formatselect | bold italic underline strikethrough | forecolor backcolor | bullist numlist | lineheight outdent indent | alignleft aligncenter alignright alignjustify | table link' +
toolbarSuffix,
menubar: 'file edit view insert format tools table',
// 补充:让字体和字号选择器更有序
fontsize_formats: '12px 14px 16px 18px 24px 36px 48px',
@@ -158,6 +164,16 @@ autoInlineStyles(htmlContent) {
});
}
if (hasEmptyBtn) {
editor.ui.registry.addButton('clearButton', {
text: 'Empty',
onAction() {
editor.setContent('');
vueInstance.$emit('input', '');
}
});
}
// 统一给自定义按钮加样式(无按钮时不会产生影响)
CommonJS.inTinymceButtonClass();
},
@@ -177,7 +193,6 @@ autoInlineStyles(htmlContent) {
}
});
},
// 假设 rawHtml 是你那一大串完整的 HTML
prepareContentForEditor(rawHtml) {
// 1. 分离出 Head 及其之前的部分 (包含 doctype, html, head)

View File

@@ -29,7 +29,7 @@
<span v-html="renderText(processedItem.title)"></span>
</div>
<table border="1" class="custom-table-core">
<table class="custom-table-core">
<tbody>
<tr v-for="(row, rIdx) in processedItem.table.tableHeader" :key="'h-' + rIdx" class="table-header-row">
<td v-for="(cell, cIdx) in row" :key="'hc-'+cIdx" :rowspan="cell.rowspan" :colspan="cell.colspan">
@@ -201,7 +201,7 @@ export default {
min-width: 794px; /* A4 宽度参考 */
max-width: 90vw;
background-color: #fff;
padding: 10px 20px;
padding: 2.54cm 1.91cm;
box-sizing: border-box;
box-shadow: 0 12px 40px rgba(0,0,0,0.5);
border-radius: 4px;
@@ -243,8 +243,20 @@ export default {
}
.custom-table-core {
width: 100%;
width: 98.3%;
margin: 0 auto;
border-collapse: collapse;
border-spacing: 0;
table-layout: auto;
font-family: 'Charis SIL', serif;
font-size: 7.5pt;
}
.table-paper-view ::v-deep .custom-table-core tr td {
padding: 0 0.19cm;
line-height: 10pt;
vertical-align: middle;
text-align: left;
}
.font {
@@ -258,22 +270,56 @@ export default {
text-align: center;
font-weight: bold;
color: rgb(210, 90, 90);
font-size: 14px;
font-size: 13px;
font-family: 'Charis SIL', serif;
line-height: 13px;
margin: 0;
padding: 0;
}
.imageTitle {
text-align: justify;
}
.tableNote, .imageNote {
color: #333;
margin-top: 15px;
font-size: 7.5pt;
font-family: 'Charis SIL', serif;
line-height: 10pt;
}
.imageNote {
text-align: center;
text-align: justify;
}
.oddColor td {
background: rgb(250, 231, 232) !important;
}
.table-paper-view ::v-deep .custom-table-core tr.table-header-row td span + span::before {
content: ' ';
}
.table-paper-view ::v-deep .custom-table-core tr.table-header-row td {
font-size: 7.5pt;
font-family: 'Charis SIL', serif;
border-left: none;
border-right: none;
border-top: 0.5pt solid #000;
border-bottom: 0.5pt solid #000;
}
.table-paper-view ::v-deep .custom-table-core tr.table-content-row td {
font-size: 7.5pt;
font-family: 'Charis SIL', serif;
border: none;
}
.table-paper-view ::v-deep .custom-table-core tr.table-content-row:last-child td {
border-bottom: 0.5pt solid #000;
}
.table-fade-enter-active, .table-fade-leave-active { transition: opacity 0.3s ease; }
.table-fade-enter, .table-fade-leave-to { opacity: 0; }

View File

@@ -74,6 +74,85 @@
<i class="el-icon-document base-margin" :style="{ '--m-r': '2px' }"> </i>
{{ $t('commonTable.BatchAddcontent') }}
</li>
<li
v-if="zyModeEnabled"
@click="handleExportManuscriptWord"
class="base-font-size base-bg-imp base-padding-all"
:style="{ '--f-s': '12px', '--f-c': '#333' }"
>
<i
class="el-icon-download base-margin"
:style="{ '--m-r': '2px' }"
v-if="!exportingManuscriptWord"
></i>
<i
class="el-icon-loading base-margin"
:style="{ '--m-r': '2px' }"
v-else
></i>
{{ $t('commonTable.exportWord') }}
</li>
<li
v-if="zyModeEnabled"
@click="handleDownloadReferencesHtml"
class="base-font-size base-bg-imp base-padding-all"
:style="{ '--f-s': '12px', '--f-c': '#333' }"
>
<i
class="el-icon-document base-margin"
:style="{ '--m-r': '2px' }"
v-if="!referencesHtmlLoading"
></i>
<i
class="el-icon-loading base-margin"
:style="{ '--m-r': '2px' }"
v-else
></i>
{{ $t('commonTable.refHtmlDownload') }}
</li>
<li
v-if="zyModeEnabled"
@click="triggerReferencesHtmlToWord"
class="base-font-size base-bg-imp base-padding-all"
:style="{ '--f-s': '12px', '--f-c': '#333' }"
>
<i
class="el-icon-upload2 base-margin"
:style="{ '--m-r': '2px' }"
v-if="!exportingReferencesHtmlWord"
></i>
<i
class="el-icon-loading base-margin"
:style="{ '--m-r': '2px' }"
v-else
></i>
{{ $t('commonTable.refHtmlToWord') }}
</li>
<input
ref="referencesHtmlFileInput"
type="file"
accept=".html,text/html"
style="display: none"
@change="handleReferencesHtmlToWord"
/>
<li
v-if="zyModeEnabled"
@click="handleOpenCitationRelevance"
class="base-font-size base-bg-imp base-padding-all"
:style="{ '--f-s': '12px', '--f-c': '#333' }"
>
<i
class="el-icon-connection base-margin"
:style="{ '--m-r': '2px' }"
v-if="!citationRelevanceLoading"
></i>
<i
class="el-icon-loading base-margin"
:style="{ '--m-r': '2px' }"
v-else
></i>
{{ $t('commonTable.citeRelevanceDetect') }}
</li>
</ul>
</div>
<li
@@ -1064,6 +1143,18 @@ import { TableUtils } from '@/common/js/TableUtils';
import { debounce, throttle } from '@/common/js/debounce';
import { tableStyle, commonWordStyle } from '@/utils/tinymceStyles';
import LatexDataPanel from './LatexDataPanel.vue';
import { downloadManuscriptWord, fetchManuscriptReferenceList } from '@/utils/exportManuscriptWord';
import {
buildReferencesHtmlLabels,
downloadReferencesEditableHtml,
parseReferencesEditableHtml
} from '@/utils/manuscriptReferenceHtml';
import {
buildCitationReviewQueue,
buildCitationReviewHtmlLabels,
downloadCitationReviewHtml
} from '@/utils/manuscriptCitationRelevance';
import { isZyModeEnabled } from '@/utils/zyMode';
export default {
name: 'tinymce',
components: {
@@ -1076,6 +1167,9 @@ export default {
articleId: {
default: ''
},
pArticleId: {
default: ''
},
isPreview: {
type: Boolean,
default() {
@@ -1185,6 +1279,11 @@ export default {
imagePath: require('@/assets/img/carriageReturn.png'), // 或者你可以设置其他路径
scrollPosition: 0,
wordList: [],
exportingManuscriptWord: false,
referencesHtmlLoading: false,
exportingReferencesHtmlWord: false,
citationRelevanceLoading: false,
manuscriptReferences: [],
proofreadingList: [],
commentList: [],
visibleDivs: [], // 存储当前可见的 div id
@@ -1309,6 +1408,9 @@ export default {
}
},
computed: {
zyModeEnabled() {
return isZyModeEnabled(this.$route);
},
sortedProofreadingList() {
const order = [2, 1, 3];
const rank = { 2: 0, 1: 1, 3: 2 };
@@ -1444,6 +1546,170 @@ export default {
this.editors = {};
},
methods: {
async handleExportManuscriptWord() {
if (this.exportingManuscriptWord) {
return;
}
if (!this.wordList || !this.wordList.length) {
this.$message.warning(this.$t('commonTable.exportManuscriptEmpty') || 'No content to export.');
return;
}
this.exportingManuscriptWord = true;
try {
await downloadManuscriptWord(this.wordList, this.mediaUrl, 'manuscript', {
fetchReferences: true,
apiClient: this.$api,
articleId: this.articleId,
pArticleId: this.pArticleId
});
this.$message.success(this.$t('commonTable.exportManuscriptSuccess') || 'Word downloaded.');
} catch (err) {
console.error(err);
if (err && err.message === 'NO_CONTENT') {
this.$message.warning(this.$t('commonTable.exportManuscriptEmpty') || 'No content to export.');
} else {
this.$message.error(this.$t('commonTable.exportManuscriptFail') || 'Failed to export Word.');
}
} finally {
this.exportingManuscriptWord = false;
}
},
async handleDownloadReferencesHtml() {
if (this.referencesHtmlLoading) {
return;
}
if (!this.$api) {
this.$message.error(this.$t('commonTable.refHtmlLoadFail'));
return;
}
this.referencesHtmlLoading = true;
try {
let references = this.manuscriptReferences;
if (!references || !references.length) {
references = await fetchManuscriptReferenceList(this.$api, this.articleId, this.pArticleId);
this.manuscriptReferences = references || [];
}
if (!references || !references.length) {
this.$message.warning(this.$t('commonTable.refHtmlEmpty'));
return;
}
const labels = buildReferencesHtmlLabels(this.$t.bind(this));
const fileName = 'references-editor' + (this.articleId ? '-' + this.articleId : '') + '.html';
downloadReferencesEditableHtml(references, labels, fileName);
this.$message.success(this.$t('commonTable.refHtmlDownloadSuccess'));
} catch (err) {
console.error(err);
this.$message.error(this.$t('commonTable.refHtmlLoadFail'));
} finally {
this.referencesHtmlLoading = false;
}
},
triggerReferencesHtmlToWord() {
if (this.exportingReferencesHtmlWord) {
return;
}
const input = this.$refs.referencesHtmlFileInput;
if (input) {
input.value = '';
input.click();
}
},
handleReferencesHtmlToWord(event) {
const input = event && event.target;
const file = input && input.files && input.files[0];
if (!file) {
return;
}
if (!this.wordList || !this.wordList.length) {
this.$message.warning(this.$t('commonTable.exportManuscriptEmpty'));
if (input) {
input.value = '';
}
return;
}
this.exportingReferencesHtmlWord = true;
const reader = new FileReader();
reader.onload = async () => {
try {
const referenceHtmlItems = parseReferencesEditableHtml(String(reader.result || ''));
if (!referenceHtmlItems.length) {
this.$message.warning(this.$t('commonTable.refHtmlParseEmpty'));
return;
}
await downloadManuscriptWord(this.wordList, this.mediaUrl, 'manuscript', {
fetchReferences: false,
referenceHtmlItems: referenceHtmlItems,
apiClient: this.$api,
articleId: this.articleId,
pArticleId: this.pArticleId
});
this.$message.success(this.$t('commonTable.refHtmlToWordSuccess'));
} catch (err) {
console.error(err);
if (err && err.message === 'NO_CONTENT') {
this.$message.warning(this.$t('commonTable.exportManuscriptEmpty'));
} else {
this.$message.error(this.$t('commonTable.refHtmlToWordFail'));
}
} finally {
this.exportingReferencesHtmlWord = false;
if (input) {
input.value = '';
}
}
};
reader.onerror = () => {
this.exportingReferencesHtmlWord = false;
if (input) {
input.value = '';
}
this.$message.error(this.$t('commonTable.refHtmlToWordFail'));
};
reader.readAsText(file, 'UTF-8');
},
async handleOpenCitationRelevance() {
if (this.citationRelevanceLoading) {
return;
}
if (!this.wordList || !this.wordList.length) {
this.$message.warning(this.$t('commonTable.citeRelevanceNoContent'));
return;
}
if (!this.$api) {
this.$message.error(this.$t('commonTable.citeRelevanceLoadFail'));
return;
}
this.citationRelevanceLoading = true;
try {
let references = this.manuscriptReferences;
if (!references || !references.length) {
references = await fetchManuscriptReferenceList(this.$api, this.articleId, this.pArticleId);
this.manuscriptReferences = references || [];
}
const items = buildCitationReviewQueue(this.wordList, this.manuscriptReferences);
if (!items.length) {
this.$message.warning(this.$t('commonTable.citeRelevanceNoCitations'));
return;
}
const labels = buildCitationReviewHtmlLabels(this.$t.bind(this));
const fileName = 'citation-relevance' + (this.articleId ? '-' + this.articleId : '') + '.html';
downloadCitationReviewHtml(items, labels, fileName);
this.$message.success(this.$t('commonTable.citeRelevanceDownloadHtmlSuccess'));
} catch (err) {
console.error(err);
this.$message.error(this.$t('commonTable.citeRelevanceLoadFail'));
} finally {
this.citationRelevanceLoading = false;
}
},
getInvolvedPMain(range) {
// 1. 找到起始节点所属的 .pMain
let startNode = range.startContainer;

View File

@@ -65,6 +65,24 @@
style="width: 200px; float: right; padding: 10px; height: calc(100% - 28px); box-sizing: border-box; overflow-y: auto"
class="arrlist"
>
<span
v-if="zyModeEnabled && currentMenu == 1 && exportableImages.length > 0"
class="tables-panel-download-icon"
@click.stop="downloadAllFiguresWord"
>
<el-tooltip effect="dark" content="Download All Figures Word" placement="left">
<i :class="exportingAllFiguresWord ? 'el-icon-loading' : 'el-icon-download'"></i>
</el-tooltip>
</span>
<span
v-if="zyModeEnabled && currentMenu == 2 && tables.length > 0"
class="tables-panel-download-icon"
@click.stop="downloadAllTablesWord"
>
<el-tooltip effect="dark" content="Download All Tables Word" placement="left">
<i :class="exportingAllWord ? 'el-icon-loading' : 'el-icon-download'"></i>
</el-tooltip>
</span>
<ul style="width: 100%; height: auto">
<li v-show="currentMenu == 1">
<div style="" class="go-content-charts-item-box" :class="isCollapse ? 'double' : 'single'">
@@ -137,7 +155,15 @@
</a>
</span>
<p>
<span style="" @click="edit(img, 'img')">
<span v-if="zyModeEnabled && isFigureExportable(img)" @click.stop="downloadSingleFigureWord(img)">
<el-tooltip effect="dark" content="Download Word" placement="top">
<i
:class="exportingFigureId === (img.article_image_id || img.ami_id) ? 'el-icon-loading' : 'el-icon-download'"
style="color: #909399; font-size: 12px"
></i>
</el-tooltip>
</span>
<span style="margin-left: 6px" @click="edit(img, 'img')">
<i class="el-icon-edit" style="color: #409eff; font-size: 12px"></i>
</span>
<span style="margin-left: 6px" @click="handledelete(img, 'img')">
@@ -329,7 +355,15 @@
</el-tooltip> </span
></span>
<p>
<span style="" @click="edit(table, 'table')">
<span v-if="zyModeEnabled && !isMathFormulaTable(table)" @click.stop="downloadSingleTableWord(table)">
<el-tooltip effect="dark" content="Download Word" placement="top">
<i
:class="exportingTableId === table.article_table_id ? 'el-icon-loading' : 'el-icon-download'"
style="color: #909399; font-size: 12px"
></i>
</el-tooltip>
</span>
<span style="margin-left: 6px" @click="edit(table, 'table')">
<i class="el-icon-edit" style="color: #409eff; font-size: 12px"></i>
</span>
<span style="margin-left: 6px" @click="handledelete(table, 'table')">
@@ -475,6 +509,13 @@
import DynamicTable from './DynamicTable.vue';
import { mediaUrl } from '@/common/js/commonJS.js';
import { isMathFormulaTableRecord, normalizeLatexTableApiResponse } from '@/utils/mathFormulaModule';
import { downloadAllTablesWord as exportAllTablesWordFile, downloadTableWord, prepareTableExportItem } from '@/utils/exportTableWord';
import {
downloadAllFiguresWord as exportAllFiguresWordFile,
downloadFigureWord,
isExportableFigureItem as canExportFigureItem
} from '@/utils/exportFigureWord';
import { isZyModeEnabled } from '@/utils/zyMode';
export default {
props: ['articleId', 'imgWidth', 'imgHeight', 'scale', 'isEdit', 'isShowEdit', 'urlList', 'content'],
@@ -505,12 +546,24 @@ export default {
mathFormulasList: [],
tablesHtml: [],
imagesHtml: [],
activeNames: ['images', 'tables']
activeNames: ['images', 'tables'],
exportingAllWord: false,
exportingTableId: null,
exportingAllFiguresWord: false,
exportingFigureId: null
};
},
components: {
DynamicTable
},
computed: {
zyModeEnabled() {
return isZyModeEnabled(this.$route);
},
exportableImages() {
return (this.images || []).filter((img) => canExportFigureItem(img));
}
},
directives: {
// 注册一个局部的自定义指令 v-focus
focus: {
@@ -526,6 +579,9 @@ export default {
changeIsCollapse(e) {
localStorage.setItem('isCollapse', e);
},
isFigureExportable(item) {
return canExportFigureItem(item);
},
isShowEditComment() {
if (localStorage.getItem('U_role')) {
var identity = localStorage.getItem('U_role');
@@ -930,7 +986,85 @@ if(newData.ami_id) {
},
openPreview(index, type,data) {
this.$refs.myTableModal.open(type,data);
},
async downloadAllTablesWord() {
if (!this.tables.length) {
this.$message.warning('No tables to export.');
return;
}
this.exportingAllWord = true;
try {
await exportAllTablesWordFile(this.tables, 'tables');
this.$message.success('Word downloaded.');
} catch (err) {
console.error(err);
if (err && err.message === 'NO_TABLES') {
this.$message.warning('No exportable tables found.');
} else {
this.$message.error('Failed to export Word.');
}
} finally {
this.exportingAllWord = false;
}
},
async downloadSingleTableWord(table) {
if (!table || this.isMathFormulaTable(table)) {
this.$message.warning('This table cannot be exported.');
return;
}
const tableId = table.article_table_id || table.amt_id;
this.exportingTableId = tableId;
try {
const prepared = prepareTableExportItem(table);
if (!prepared) {
this.$message.warning('Failed to prepare table.');
return;
}
await downloadTableWord(prepared, table.title);
this.$message.success('Word downloaded.');
} catch (err) {
console.error(err);
this.$message.error('Failed to export Word.');
} finally {
this.exportingTableId = null;
}
},
async downloadAllFiguresWord() {
if (!this.exportableImages.length) {
this.$message.warning('No figures to export.');
return;
}
this.exportingAllFiguresWord = true;
try {
await exportAllFiguresWordFile(this.images, this.mediaUrl, 'figures');
this.$message.success('Word downloaded.');
} catch (err) {
console.error(err);
if (err && err.message === 'NO_FIGURES') {
this.$message.warning('No exportable figures found.');
} else {
this.$message.error('Failed to export Word.');
}
} finally {
this.exportingAllFiguresWord = false;
}
},
async downloadSingleFigureWord(img) {
if (!img || !canExportFigureItem(img)) {
this.$message.warning('This figure cannot be exported.');
return;
}
const figureId = img.article_image_id || img.ami_id;
this.exportingFigureId = figureId;
try {
await downloadFigureWord(img, this.mediaUrl, img.title);
this.$message.success('Word downloaded.');
} catch (err) {
console.error(err);
this.$message.error('Failed to export Word.');
} finally {
this.exportingFigureId = null;
}
},
async getWordimgList() {
var that = this;
@@ -1049,6 +1183,25 @@ if(newData.ami_id) {
.el-menu-vertical-demo {
width: 60px;
}
.arrlist {
position: relative;
}
.tables-panel-download-icon {
position: absolute;
top: 6px;
right: 8px;
z-index: 2;
color: #909399;
font-size: 16px;
line-height: 1;
cursor: pointer;
}
.tables-panel-download-icon:hover {
color: #409eff;
}
.el-menu-vertical-demo li {
display: flex;
flex-direction: column;

View File

@@ -297,19 +297,21 @@
<p>
{{ scope.row.author }}&nbsp;<span v-html="formatTitle(scope.row.title)"></span>. &nbsp;<em>{{ scope.row.joura }}</em
>.&nbsp;<span :class="getJournalDateno(scope.row.dateno, 'title')">{{ scope.row.dateno }}</span
>.<br />
>.<reference-search-links :row="scope.row"></reference-search-links><br />
</p>
<a class="doiLink" :href="scope.row.doilink" target="_blank">{{ scope.row.doilink }}</a>
</div>
<!-- book 形式 -->
<div style="text-align: left" v-if="scope.row.refer_type == 'book'" class="reference-item">
<p>{{ scope.row.author }}&nbsp;<span v-html="formatTitle(scope.row.title)"></span>.&nbsp;{{ scope.row.dateno }}.&nbsp;<br /></p>
<p>
{{ scope.row.author }}&nbsp;<span v-html="formatTitle(scope.row.title)"></span>.&nbsp;{{ scope.row.dateno }}.&nbsp;<reference-search-links :row="scope.row"></reference-search-links><br />
</p>
<a class="doiLink" :href="scope.row.isbn" target="_blank">{{ scope.row.isbn }}</a>
</div>
<!-- other 形式 -->
<p class="wrongLine reference-item" style="text-align: left" v-if="scope.row.refer_type == 'other'">
<span v-html="formatTitle(scope.row.refer_frag)"></span>
<reference-search-links :row="scope.row"></reference-search-links>
</p>
</template>
</el-table-column>
@@ -895,6 +897,7 @@
<script>
import VueUeditorWrap from 'vue-ueditor-wrap'; // ES6 Module
import ReferenceSearchLinks from '@/components/page/components/ReferenceSearchLinks.vue';
/** 单条引用 records[].status0 待检测 2 已完成 3 检测失败 */
const REF_RELEVANCE_RECORD_STATUS_PENDING = 0;
@@ -1450,8 +1453,7 @@ export default {
return title.replace(reg, (match) => {
return `<span style="color: red; font-weight: bold;">${match}</span>`;
});
}
,
},
getJournalDateno(dateno, type) {
if (dateno && typeof dateno === 'string') {
const hasInvalidColon = !dateno.includes(':') || (dateno.includes(':') && dateno.split(':').pop().trim() === '');
@@ -5244,7 +5246,8 @@ export default {
}
},
components: {
VueUeditorWrap
VueUeditorWrap,
ReferenceSearchLinks
},
watch: {
role: {

View File

@@ -34,7 +34,7 @@
<el-dialog
:title="articleContextDialogTitle"
:visible.sync="articleContextDialogVisible"
width="1100px"
width="1140px"
top="8vh"
append-to-body
custom-class="article-context-dialog"
@@ -54,23 +54,16 @@
<el-button type="primary" size="small" :loading="articleContextLoading" @click="loadArticleContext()">
{{ $t('mailboxSend.articleContextSearch') }}
</el-button>
<el-button
v-if="articleContext.loaded && hasArticleTemplatePlaceholders"
size="small"
type="warning"
plain
icon="el-icon-magic-stick"
@click="applyArticleTemplateVariables"
>
{{ $t('mailboxSend.articleContextReplaceVars') }}
</el-button>
</div>
<div v-if="articleContext.loaded" class="article-context-loaded-hint">
<div class="article-loaded-line">
<span class="article-loaded-sn">SN {{ articleContext.acceptSn || '—' }}</span>
<div v-if="articleContext.loaded" class="article-context-meta-card">
<div class="article-context-meta-card__row">
<span class="article-meta-label">SN</span>
<span class="article-loaded-sn">{{ articleContext.acceptSn || '—' }}</span>
</div>
<div v-if="articleContext.title" class="article-context-meta-card__title">
{{ articleContext.title }}
</div>
<div v-if="articleContext.title" class="article-loaded-title">{{ articleContext.title }}</div>
</div>
<div v-else-if="!articleContextLoading" class="article-contact-empty article-contact-empty--hint">
{{ $t('mailboxSend.articleContextSearchHint') }}
@@ -123,7 +116,7 @@
/>
</th>
<th class="review_table_index">No.</th>
<th>{{ $t('mailboxSend.tableName') }}</th>
<th class="review_table_name">{{ $t('mailboxSend.tableName') }}</th>
<th class="review_table_role">{{ $t('mailboxSend.tableAuthorRole') }}</th>
</tr>
</thead>
@@ -131,7 +124,12 @@
<tr
v-for="(author, idx) in articleContext.authors"
:key="author.key"
:class="{ 'review_table_row--disabled': !hasContactEmail(author) }"
class="review_table_row--clickable"
:class="{
'review_table_row--disabled': !hasContactEmail(author),
'review_table_row--selected': isContactSelected('authors', author.key)
}"
@click="handleContactRowClick('authors', author, $event)"
>
<td class="review_table_check">
<el-checkbox
@@ -141,17 +139,36 @@
/>
</td>
<td class="review_table_index">{{ idx + 1 }}</td>
<td>
<td class="review_table_name">
<div class="author-name-email-cell">
<div class="author-name-email-cell__name">{{ author.name || '—' }}</div>
<div v-if="author.email" class="author-name-email-cell__email">
<span class="is-clickable" @click="addContactToTo(author)">{{ author.email }}</span>
<span class="is-clickable" @click.stop="addContactToTo(author)">{{ author.email }}</span>
<i class="el-icon-document-copy article-copy-icon" :title="$t('mailboxSend.copyText')" @click.stop="copyContactText(author.email)"></i>
</div>
<div v-else class="author-name-email-cell__email reviewer-no-email">{{ $t('mailboxSend.articleContextNoEmail') }}</div>
</div>
</td>
<td class="review_table_role">{{ formatAuthorRoles(author.roles) }}</td>
<td class="review_table_role">
<div v-if="formatAuthorRoles(author.roles) !== '—'" class="author-role-tags">
<el-tag
v-if="(author.roles || []).indexOf('firstAuthor') >= 0"
size="mini"
type="info"
effect="plain"
>
{{ $t('mailboxSend.roleFirstAuthor') }}
</el-tag>
<el-tag
v-if="(author.roles || []).indexOf('correspondingAuthor') >= 0"
size="mini"
effect="plain"
>
{{ $t('mailboxSend.roleCorrespondingAuthor') }}
</el-tag>
</div>
<span v-else></span>
</td>
</tr>
</tbody>
</table>
@@ -184,7 +201,12 @@
<tr
v-for="(row, idx) in articleContext.reviewRows"
:key="row.key"
:class="{ 'review_table_row--disabled': !hasContactEmail(row) }"
class="review_table_row--clickable"
:class="{
'review_table_row--disabled': !hasContactEmail(row),
'review_table_row--selected': isContactSelected('review', row.key)
}"
@click="handleContactRowClick('review', row, $event)"
>
<td class="review_table_check">
<el-checkbox
@@ -243,7 +265,12 @@
<tr
v-for="(row, idx) in articleContext.finalReviewers"
:key="row.key"
:class="{ 'review_table_row--disabled': !hasContactEmail(row) }"
class="review_table_row--clickable"
:class="{
'review_table_row--disabled': !hasContactEmail(row),
'review_table_row--selected': isContactSelected('final', row.key)
}"
@click="handleContactRowClick('final', row, $event)"
>
<td class="review_table_check">
<el-checkbox
@@ -269,6 +296,15 @@
<span class="article-context-selected-tab is-active">
{{ $t('mailboxSend.to') }} ({{ articleSelectedTotalCount }})
</span>
<el-button
v-if="articleSelectedTotalCount"
type="text"
size="mini"
class="article-context-selected-clear"
@click="clearArticleContacts(articleContactActiveTab)"
>
{{ $t('mailboxSend.articleContextClearAll') }}
</el-button>
</div>
<div class="article-context-selected-list">
<div v-if="!articleSelectedContactList.length" class="article-context-selected-empty">
@@ -1180,6 +1216,17 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
isContactSelected(module, key) {
return (this.articleContactSelected[module] || []).indexOf(key) >= 0;
},
handleContactRowClick(module, contact, event) {
if (!contact || !this.hasContactEmail(contact)) return;
if (
event.target.closest(
'.el-checkbox, .el-checkbox__input, .article-copy-icon, .is-clickable, a, button, input, label'
)
) {
return;
}
this.toggleContactSelected(module, contact.key, !this.isContactSelected(module, contact.key));
},
toggleContactSelected(module, key, checked) {
if (checked) {
const contact = this.getModuleContactList(module).find(function (item) {
@@ -2005,27 +2052,48 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
.article-context-search-bar {
display: flex;
gap: 10px;
margin-bottom: 20px;
margin-bottom: 12px;
flex-wrap: wrap;
align-items: center;
}
.article-context-dialog-body {
max-height: 60vh;
max-height: 62vh;
overflow-y: auto;
padding-right: 10px;
padding-right: 4px;
}
.article-context-loaded-hint {
display: flex;
align-items: flex-start;
gap: 4px;
flex-wrap: wrap;
.article-context-meta-card {
margin-bottom: 12px;
padding: 10px 12px;
background: #f5f7fa;
border: 1px solid #ebeef5;
border-radius: 4px;
}
.article-context-meta-card__row {
display: flex;
align-items: center;
gap: 8px;
}
.article-meta-label {
display: inline-block;
padding: 0 6px;
font-size: 11px;
line-height: 20px;
color: #909399;
background: #fff;
border: 1px solid #dcdfe6;
border-radius: 3px;
}
.article-context-meta-card__title {
margin-top: 6px;
font-size: 13px;
line-height: 1.5;
color: #606266;
flex-direction: column;
width: 100%;
word-break: break-word;
}
.article-template-vars-bar {
@@ -2045,14 +2113,7 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
.article-loaded-sn {
font-weight: 600;
color: #303133;
}
.article-loaded-title {
width: 100%;
color: #606266;
line-height: 1.5;
word-break: break-word;
white-space: normal;
font-size: 13px;
}
.article-contact-empty--hint {
@@ -2064,7 +2125,8 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
.article-context-layout {
display: flex;
min-height: 320px;
min-height: 360px;
max-height: 48vh;
border: 1px solid #ebeef5;
border-radius: 4px;
overflow: hidden;
@@ -2109,13 +2171,13 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
.article-context-main {
flex: 1;
min-width: 0;
padding: 12px;
overflow-x: auto;
padding: 0;
overflow: auto;
border-right: 1px solid #ebeef5;
}
.article-context-selected {
width: 280px;
width: 300px;
flex-shrink: 0;
display: flex;
flex-direction: column;
@@ -2123,10 +2185,19 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
}
.article-context-selected-head {
padding: 12px 14px 0;
display: flex;
align-items: center;
justify-content: space-between;
gap: 8px;
padding: 10px 14px 0;
border-bottom: 1px solid #ebeef5;
}
.article-context-selected-clear {
padding: 0;
margin-bottom: 6px;
}
.article-context-selected-tab {
display: inline-block;
padding-bottom: 10px;
@@ -2144,7 +2215,6 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
.article-context-selected-list {
flex: 1;
min-height: 0;
max-height: 360px;
overflow-y: auto;
padding: 8px 0;
}
@@ -2354,22 +2424,30 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
}
.overflow-x-auto {
overflow-x: auto;
overflow: auto;
height: 100%;
}
.review_table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
margin-bottom: 20px;
margin-bottom: 0;
background-color: #fff;
}
.review_table thead th {
position: sticky;
top: 0;
z-index: 1;
}
.review_table th,
.review_table td {
padding: 8px 12px;
padding: 10px 12px;
border-bottom: 1px solid #ebeef5;
text-align: left;
font-size: 14px;
font-size: 13px;
word-break: break-word;
white-space: normal;
vertical-align: top;
@@ -2394,6 +2472,22 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
color: #c0c4cc;
}
.review_table_row--clickable:not(.review_table_row--disabled) {
cursor: pointer;
}
.review_table_row--selected td {
background: #ecf5ff;
}
.review_table_row--clickable:not(.review_table_row--disabled):hover td {
background: #f5f7fa;
}
.review_table_row--selected.review_table_row--clickable:not(.review_table_row--disabled):hover td {
background: #e6f1fc;
}
.review_table_row--disabled .reviewer-score {
color: #d4d7de;
}
@@ -2416,12 +2510,34 @@ import { applyReviewNotifyTemplateVariables } from '@/utils/mailTemplatePreview'
white-space: nowrap;
}
.review_table_name {
width: auto;
min-width: 180px;
}
.review_table_role {
min-width: 220px;
white-space: nowrap;
width: 148px;
min-width: 148px;
max-width: 148px;
word-break: normal;
}
.author-role-tags {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.author-role-tags .el-tag {
max-width: 100%;
white-space: normal;
height: auto;
line-height: 1.4;
padding-top: 2px;
padding-bottom: 2px;
}
.reviewer-name-cell {
min-width: 220px;
}